package converter

import (
	"encoding/json"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"
	kbservice "github.com/tidbcloud/AgentTeam/internal/service/kb"
	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

func ToKnowledgeBase(base kb.KnowledgeBase, sources []kb.Source) dto.KnowledgeBase {
	workingRevision := base.WorkingRevision
	if base.PublicationState == kbservice.KBPublicationOfficial {
		workingRevision = ""
	}
	return dto.KnowledgeBase{ID: base.ID, WorkspaceID: base.WorkspaceID, GroupID: base.GroupID, Group: ToKnowledgeBaseGroupPointer(base.Group), Tags: ToKnowledgeBaseTags(base.Tags), SourceCount: base.SourceCount, Name: base.Name, Description: base.Description, StorageScope: base.StorageScope, StorageOwnerLogin: base.StorageOwnerLogin, DefaultWorkspaceMemberRole: base.DefaultWorkspaceMemberRole, DefaultRepositoryAccessProfile: base.DefaultRepositoryAccessProfile, AllowKnowledgeQAWithoutSourceAccess: base.AllowKnowledgeQAWithoutSourceAccess, PublicationState: base.PublicationState, WorkingRevision: workingRevision, PublishedRevision: base.PublishedRevision, PublishedAt: optionalTime(base.PublishedAt), SetupSummary: ToKnowledgeBaseSetupSummary(base.SetupSummary), Sources: ToKBSources(sources), RetrievalPolicy: decodeJSONMap(base.RetrievalPolicy), SourceCollectionState: decodeJSONMap(base.SourceCollectionState), CreatedAt: base.CreatedAt.UTC().Format(timeLayout), UpdatedAt: base.UpdatedAt.UTC().Format(timeLayout), LastAccessedAt: optionalTime(base.LastAccessedAt), IsActive: base.IsActive, Pinned: base.Pinned, PinnedAt: optionalTime(base.PinnedAt), Role: base.Role, CanManage: base.CanManage, CanEditOfficial: base.CanEditOfficial, CanConvertToDraft: base.CanConvertToDraft, RepositoryAccessProfile: base.RepositoryAccessProfile, RepositoryAccessStatus: base.RepositoryAccessStatus, CreatedBy: base.CreatedBy, UpdatedBy: base.UpdatedBy}
}

func ToKnowledgeBaseSetupSummary(summary *kb.KnowledgeBaseSetupSummary) *dto.KnowledgeBaseSetupSummary {
	if summary == nil {
		return nil
	}
	return &dto.KnowledgeBaseSetupSummary{Mode: summary.Mode, ActiveStep: summary.ActiveStep, Version: summary.Version}
}

func ToKnowledgeBaseSetup(view kbservice.KBSetupView) dto.KnowledgeBaseSetupSession {
	sources := make([]dto.KBSetupSource, 0, len(view.Sources))
	for _, source := range view.Sources {
		sources = append(sources, dto.KBSetupSource{
			SourceID: source.SourceID, SourceRevision: source.SourceRevision,
			SourceCommitSHA: source.SourceCommitSHA, WikiState: source.WikiState,
			WikiRequest:               toKBSetupGenerationRequest(source.WikiRequest),
			CanReviewWikiGeneration:   source.CanReviewWikiGeneration,
			CanWithdrawWikiGeneration: source.CanWithdrawWikiGeneration,
			DashboardState:            source.DashboardState,
		})
	}
	return dto.KnowledgeBaseSetupSession{
		ID: view.Session.ID, KnowledgeBaseID: view.Session.KnowledgeBaseID, Mode: view.Session.Mode,
		ActiveStep: view.Session.ActiveStep, WorkingRevision: view.Session.WorkingRevision,
		PublishedRevision: view.Session.PublishedRevision, Version: view.Session.Version,
		BasicInfo: dto.KBSetupBasicInfo{
			Name: view.BasicInfo.Name, Description: view.BasicInfo.Description,
			GroupID: view.BasicInfo.GroupID, TagIDs: append([]string(nil), view.BasicInfo.TagIDs...),
		},
		OfficialChangeFields:  append([]string(nil), view.OfficialChangeFields...),
		OfficialContentDigest: view.OfficialContentDigest,
		StepSummary:           view.StepSummary, Sources: sources, UpdatedAt: view.Session.UpdatedAt.UTC().Format(timeLayout),
		PublicationRequest:       toKBSetupPublicationRequest(view.PublicationRequest),
		PublicationApprovers:     toKBSetupPublicationApprovers(view.PublicationApprovers),
		CanRequestPublication:    view.CanRequestPublication,
		CanReviewPublication:     view.CanReviewPublication,
		CanWithdrawPublication:   view.CanWithdrawPublication,
		CanDirectWikiGeneration:  view.CanDirectWikiGeneration,
		CanRequestWikiGeneration: view.CanRequestWikiGeneration,
		CanDirectDashboardImport: view.CanDirectDashboardImport,
		CanSaveOfficial:          view.CanSaveOfficial, UpdatedBy: view.Session.UpdatedBy,
		CompletedAt: optionalTime(view.Session.CompletedAt),
	}
}

func toKBSetupPublicationApprovers(approvers []kbgovernance.PublicationApprover) []dto.KBSetupPublicationApprover {
	result := make([]dto.KBSetupPublicationApprover, 0, len(approvers))
	for _, approver := range approvers {
		result = append(result, dto.KBSetupPublicationApprover{
			PrincipalID: approver.PrincipalID, DisplayName: approver.DisplayName,
		})
	}
	return result
}

func ToKnowledgeBasePublicationReview(review kbservice.KBPublicationReview) dto.KnowledgeBasePublicationReview {
	sources := make([]dto.KnowledgeBasePublicationReviewSource, 0, len(review.Sources))
	for _, source := range review.Sources {
		sources = append(sources, dto.KnowledgeBasePublicationReviewSource{
			ID: source.ID, SourceType: source.SourceType, Name: source.Name,
			Description: source.Description, DefaultRef: source.DefaultRef,
			Status: source.Status, SyncStatus: source.SyncStatus, IndexStatus: source.IndexStatus,
			RevisionID: source.RevisionID, SourceCommitSHA: source.SourceCommitSHA,
			WikiRevision: source.WikiRevision, DashboardRevision: source.DashboardRevision,
			WikiDisposition: source.WikiDisposition, DashboardDisposition: source.DashboardDisposition,
		})
	}
	request := toKBSetupPublicationRequest(&review.Request)
	return dto.KnowledgeBasePublicationReview{
		KnowledgeBaseID: review.KnowledgeBaseID, Name: review.Name, Description: review.Description,
		GroupID: review.GroupID, TagIDs: review.TagIDs, Sources: sources, Request: *request,
		Approvers: toKBSetupPublicationApprovers(review.Approvers), CanDecide: review.CanDecide,
	}
}

func toKBSetupPublicationRequest(request *kb.KnowledgeBasePublicationRequest) *dto.KBSetupPublicationRequest {
	if request == nil {
		return nil
	}
	return &dto.KBSetupPublicationRequest{
		ID: request.ID, WorkingRevision: request.WorkingRevision, RequesterID: request.RequesterID,
		ApprovalID: request.ApprovalID, ApprovalVersion: request.ApprovalVersion, Status: request.Status,
		SourceCount: request.SourceCount, WikiReadyCount: request.WikiReadyCount,
		DashboardReadyCount: request.DashboardReadyCount, SkippedCount: request.SkippedCount,
		DecisionReason: request.DecisionReason, Version: request.Version,
		CreatedAt: request.CreatedAt.UTC().Format(timeLayout), UpdatedAt: request.UpdatedAt.UTC().Format(timeLayout),
		PublishedAt: optionalTime(request.PublishedAt),
	}
}

func toKBSetupGenerationRequest(request *kb.KnowledgeBaseGenerationRequest) *dto.KBSetupGenerationRequest {
	if request == nil {
		return nil
	}
	return &dto.KBSetupGenerationRequest{
		ID: request.ID, SourceRevision: request.SourceRevisionID,
		Scope: request.Scope, Language: request.Language, Reason: request.Reason,
		RequesterID: request.RequesterID, ApprovalID: request.ApprovalID,
		ApprovalVersion: request.ApprovalVersion,
		Status:          request.Status, JobID: request.JobID,
		ArtifactRevision: request.ArtifactRevision,
		DecisionReason:   request.DecisionReason, Version: request.Version,
		CreatedAt: request.CreatedAt.UTC().Format(timeLayout),
		UpdatedAt: request.UpdatedAt.UTC().Format(timeLayout),
	}
}

func ToKnowledgeBaseGroup(group kb.KnowledgeBaseGroup) dto.KnowledgeBaseGroup {
	return dto.KnowledgeBaseGroup{ID: group.ID, Name: group.Name, Description: group.Description, Color: group.Color, SortOrder: group.SortOrder, IsDefault: group.IsDefault}
}

func ToKnowledgeBaseGroupPointer(group *kb.KnowledgeBaseGroup) *dto.KnowledgeBaseGroup {
	if group == nil {
		return nil
	}
	converted := ToKnowledgeBaseGroup(*group)
	return &converted
}

func ToKnowledgeBaseGroups(groups []kb.KnowledgeBaseGroup) []dto.KnowledgeBaseGroup {
	out := make([]dto.KnowledgeBaseGroup, 0, len(groups))
	for _, group := range groups {
		out = append(out, ToKnowledgeBaseGroup(group))
	}
	return out
}

func ToKnowledgeBaseTag(tag kb.KnowledgeBaseTag) dto.KnowledgeBaseTag {
	return dto.KnowledgeBaseTag{ID: tag.ID, Name: tag.Name, Color: tag.Color, SortOrder: tag.SortOrder}
}

func ToKnowledgeBaseTags(tags []kb.KnowledgeBaseTag) []dto.KnowledgeBaseTag {
	out := make([]dto.KnowledgeBaseTag, 0, len(tags))
	for _, tag := range tags {
		out = append(out, ToKnowledgeBaseTag(tag))
	}
	return out
}

func ToKnowledgeBaseTaxonomy(taxonomy kbservice.KnowledgeBaseTaxonomy) dto.KnowledgeBaseTaxonomy {
	return dto.KnowledgeBaseTaxonomy{Groups: ToKnowledgeBaseGroups(taxonomy.Groups), Tags: ToKnowledgeBaseTags(taxonomy.Tags)}
}

func decodeJSONMap(raw string) map[string]any {
	if strings.TrimSpace(raw) == "" {
		return nil
	}
	var out map[string]any
	if err := json.Unmarshal([]byte(raw), &out); err != nil {
		return nil
	}
	return out
}

func ToKnowledgeBases(bases []kb.KnowledgeBase) []dto.KnowledgeBase {
	if bases == nil {
		return nil
	}
	out := make([]dto.KnowledgeBase, 0, len(bases))
	for _, base := range bases {
		out = append(out, ToKnowledgeBase(base, nil))
	}
	return out
}

func ToAgentAccessibleKnowledgeBase(item kbservice.AgentAccessibleKnowledgeBase) dto.AgentAccessibleKnowledgeBase {
	return dto.AgentAccessibleKnowledgeBase{
		ID:              item.ID,
		WorkspaceID:     item.WorkspaceID,
		Name:            item.Name,
		Description:     item.Description,
		AccessLevel:     item.AccessLevel,
		PermissionSetAt: item.PermissionSetAt.UTC().Format(timeLayout),
		UpdatedAt:       item.UpdatedAt.UTC().Format(timeLayout),
	}
}

func ToAgentAccessibleKnowledgeBaseGrant(item kbservice.AgentKnowledgeBaseGrantResult) dto.AgentAccessibleKnowledgeBaseGrant {
	return dto.AgentAccessibleKnowledgeBaseGrant{
		AgentID:         item.AgentID,
		KnowledgeBaseID: item.KnowledgeBaseID,
		AccessLevel:     item.AccessLevel,
	}
}

func ToAgentAccessibleKnowledgeBases(items []kbservice.AgentAccessibleKnowledgeBase) []dto.AgentAccessibleKnowledgeBase {
	if items == nil {
		return nil
	}
	out := make([]dto.AgentAccessibleKnowledgeBase, 0, len(items))
	for _, item := range items {
		out = append(out, ToAgentAccessibleKnowledgeBase(item))
	}
	return out
}

func ToAgentKnowledgeBaseAccessBatchResponse(batch kbservice.AgentKnowledgeBaseAccessBatch) dto.AgentKnowledgeBaseAccessBatchResponse {
	out := dto.AgentKnowledgeBaseAccessBatchResponse{
		Agents:           make(map[string][]dto.AgentAccessibleKnowledgeBaseForSync, len(batch.Agents)),
		SyncToken:        batch.SyncToken,
		SyncTokenExpires: optionalTime(&batch.SyncTokenExpires),
	}
	if batch.SyncTokenExpires.IsZero() {
		out.SyncTokenExpires = ""
	}
	for agentID, bases := range batch.Agents {
		converted := make([]dto.AgentAccessibleKnowledgeBaseForSync, 0, len(bases))
		for _, base := range bases {
			converted = append(converted, toAgentAccessibleKnowledgeBaseForSync(base))
		}
		out.Agents[agentID] = converted
	}
	return out
}

func toAgentAccessibleKnowledgeBaseForSync(item kbservice.AgentAccessibleKnowledgeBaseForSync) dto.AgentAccessibleKnowledgeBaseForSync {
	sources := make([]dto.AgentAccessibleKBSourceForSync, 0, len(item.Sources))
	for _, source := range item.Sources {
		repos := make([]dto.AgentAccessibleKBRepositoryForSync, 0, len(source.Repositories))
		for _, repo := range source.Repositories {
			repos = append(repos, dto.AgentAccessibleKBRepositoryForSync{
				RepoRole:      repositoryRoleForWire(repo.RepoRole),
				Repository:    ToResourceRepositoryRef(repo.Repository),
				Branch:        repo.Branch,
				AccessProfile: repo.AccessProfile,
				AccessStatus:  repo.AccessStatus,
				Writable:      repo.Writable,
			})
		}
		sources = append(sources, dto.AgentAccessibleKBSourceForSync{ID: source.ID, Name: source.Name, Description: source.Description, SourceType: source.SourceType, DefaultRef: source.DefaultRef, Repositories: repos})
	}
	return dto.AgentAccessibleKnowledgeBaseForSync{ID: item.ID, WorkspaceID: item.WorkspaceID, Name: item.Name, Description: item.Description, AccessLevel: item.AccessLevel, PermissionSetAt: item.PermissionSetAt.UTC().Format(timeLayout), UpdatedAt: item.UpdatedAt.UTC().Format(timeLayout), Sources: sources}
}

func ToKnowledgeBaseListResponse(items []kb.KnowledgeBase, groupCounts []kb.KnowledgeBaseGroupCount, page, perPage int, total int64, hasMore bool) dto.KnowledgeBaseListResponse {
	counts := make([]dto.KnowledgeBaseGroupCount, 0, len(groupCounts))
	for _, groupCount := range groupCounts {
		counts = append(counts, dto.KnowledgeBaseGroupCount{
			GroupID: groupCount.GroupID, PublicationState: groupCount.PublicationState,
			Count: groupCount.Count,
		})
	}
	return dto.KnowledgeBaseListResponse{Items: ToKnowledgeBases(items), GroupCounts: counts, Page: page, PerPage: perPage, Total: total, HasMore: hasMore}
}

func ToKnowledgeBasePin(pin kb.KnowledgeBasePin) dto.KnowledgeBasePin {
	return dto.KnowledgeBasePin{WorkspaceID: pin.WorkspaceID, PrincipalID: pin.PrincipalID, KnowledgeBaseID: pin.KnowledgeBaseID, CreatedAt: pin.CreatedAt.UTC().Format(timeLayout), UpdatedAt: pin.UpdatedAt.UTC().Format(timeLayout)}
}

func ToKBPermissionGrant(grant kbservice.KBPermissionGrant) dto.KBPermissionGrant {
	sourceAccess := make([]dto.KBPermissionRepositoryAccess, 0, len(grant.SourceRepositoryAccess))
	for _, row := range grant.SourceRepositoryAccess {
		sourceAccess = append(sourceAccess, dto.KBPermissionRepositoryAccess{
			SourceID:                         row.SourceID,
			SourceName:                       row.SourceName,
			RepoRole:                         repositoryRoleForWire(row.RepoRole),
			RepoFullName:                     row.RepoFullName,
			InheritedRepositoryAccessProfile: row.InheritedRepositoryAccessProfile,
			OverrideRepositoryAccessProfile:  row.OverrideRepositoryAccessProfile,
			EffectiveRepositoryAccessProfile: row.EffectiveRepositoryAccessProfile,
			RepositoryAccessStatus:           row.RepositoryAccessStatus,
			RepositoryAccessLastError:        row.RepositoryAccessLastError,
		})
	}
	return dto.KBPermissionGrant{ID: grant.ID, WorkspaceID: grant.WorkspaceID, KnowledgeBaseID: grant.KnowledgeBaseID, SubjectType: grant.SubjectType, SubjectID: grant.SubjectID, Role: grant.Role, RepositoryAccessProfile: grant.RepositoryAccessProfile, RepositoryAccessStatus: grant.RepositoryAccessStatus, RepositoryAccessLastError: grant.RepositoryAccessLastError, CreatedBy: grant.CreatedBy, UpdatedBy: grant.UpdatedBy, CreatedAt: grant.CreatedAt.UTC().Format(timeLayout), UpdatedAt: grant.UpdatedAt.UTC().Format(timeLayout), SourceRepositoryAccess: sourceAccess}
}

func ToKBPermissionsResponse(summary kbservice.KBPermissionsSummary) dto.KBPermissionsResponse {
	members := make([]dto.KBPermissionGrant, 0, len(summary.Members))
	for _, grant := range summary.Members {
		members = append(members, ToKBPermissionGrant(grant))
	}
	agents := make([]dto.KBPermissionGrant, 0, len(summary.Agents))
	for _, grant := range summary.Agents {
		agents = append(agents, ToKBPermissionGrant(grant))
	}
	var owner *dto.KBPermissionGrant
	if summary.Owner != nil {
		converted := ToKBPermissionGrant(*summary.Owner)
		owner = &converted
	}
	var self *dto.KBPermissionGrant
	if summary.Self != nil {
		converted := ToKBPermissionGrant(*summary.Self)
		self = &converted
	}
	return dto.KBPermissionsResponse{MyRole: summary.MyRole, CanManage: summary.CanManage, Owner: owner, Self: self, Members: members, Agents: agents}
}

func ToAgentKnowledgeBaseBulkAccessResponse(result kbservice.AgentKnowledgeBaseBulkAccessResult) dto.AgentKnowledgeBaseBulkAccessResponse {
	return dto.AgentKnowledgeBaseBulkAccessResponse{AgentID: result.AgentID, KnowledgeBaseIDs: result.KnowledgeBaseIDs, GrantedKnowledgeBaseIDs: result.GrantedKnowledgeBaseIDs, RevokedKnowledgeBaseIDs: result.RevokedKnowledgeBaseIDs}
}

func ToKBSource(source kb.Source) dto.KBSource {
	var mirror *dto.KBSourceMirrorSummary
	if source.Mirror != nil {
		converted := ToKBSourceMirrorSummary(*source.Mirror)
		mirror = &converted
	}
	return dto.KBSource{ID: source.ID, KnowledgeBaseID: source.KnowledgeBaseID, SourceType: source.SourceType, Status: source.Status, Name: source.Name, Description: source.Description, DefaultRef: source.DefaultRef, IndexingMode: source.IndexingMode, SyncStatus: source.SyncStatus, IndexStatus: source.IndexStatus, AnalysisStatus: source.AnalysisStatus, FreshnessStatus: source.FreshnessStatus, Config: source.Config, LastError: kbservice.SanitizeKBProgressMessage(source.LastError), LastSyncAt: optionalTime(source.LastSyncAt), LastIndexAt: optionalTime(source.LastIndexAt), LastAnalysisAt: optionalTime(source.LastAnalysisAt), Mirror: mirror, ImportProgress: toKBSourceImportProgress(source.ImportProgress), SourceRepositoryAccessProfile: source.SourceRepositoryAccessProfile, SourceRepositoryAccessStatus: source.SourceRepositoryAccessStatus, InsightsRepositoryAccessProfile: source.InsightsRepositoryAccessProfile, InsightsRepositoryAccessStatus: source.InsightsRepositoryAccessStatus, CreatedAt: source.CreatedAt.UTC().Format(time.RFC3339Nano), UpdatedAt: source.UpdatedAt.UTC().Format(time.RFC3339Nano)}
}

func toKBSourceImportProgress(progress *kb.SourceImportProgress) *dto.KBSourceImportProgress {
	if progress == nil {
		return nil
	}
	return &dto.KBSourceImportProgress{
		State:          progress.State,
		Severity:       progress.Severity,
		Phase:          progress.Phase,
		Stage:          progress.Stage,
		Message:        progress.Message,
		Percent:        progress.Percent,
		CurrentJobID:   progress.CurrentJobID,
		CurrentJobType: progress.CurrentJobType,
		StartedAt:      optionalTime(progress.StartedAt),
		UpdatedAt:      optionalTime(progress.UpdatedAt),
		FinishedAt:     optionalTime(progress.FinishedAt),
		LastError:      progress.LastError,
		Steps:          toKBSourceImportProgressSteps(progress.Steps),
	}
}

func toKBSourceImportProgressSteps(steps []kb.SourceImportProgressStep) []dto.KBSourceImportProgressStep {
	if steps == nil {
		return nil
	}
	out := make([]dto.KBSourceImportProgressStep, 0, len(steps))
	for _, step := range steps {
		out = append(out, dto.KBSourceImportProgressStep{
			ID:         step.ID,
			Phase:      step.Phase,
			Stage:      step.Stage,
			Status:     step.Status,
			Optional:   step.Optional,
			Message:    step.Message,
			Error:      step.Error,
			StartedAt:  optionalTime(step.StartedAt),
			UpdatedAt:  optionalTime(step.UpdatedAt),
			FinishedAt: optionalTime(step.FinishedAt),
		})
	}
	return out
}

func ToKBSourceMirrorSummary(mirror kb.SourceMirror) dto.KBSourceMirrorSummary {
	return dto.KBSourceMirrorSummary{
		SourceID:           mirror.SourceID,
		UpstreamType:       mirror.UpstreamType,
		RemoteURL:          mirror.RemoteURL,
		Credential:         toKBSourceMirrorCredentialSummary(mirror.CredentialSummary),
		DefaultRef:         mirror.DefaultRef,
		IntervalSeconds:    mirror.IntervalSeconds,
		Enabled:            mirror.Enabled,
		SyncStatus:         mirror.SyncStatus,
		LastSyncAt:         optionalTime(mirror.LastSyncAt),
		LastSuccessAt:      optionalTime(mirror.LastSuccessAt),
		NextSyncAt:         optionalTime(mirror.NextSyncAt),
		LastError:          kbservice.SanitizeKBProgressMessage(mirror.LastError),
		LastMirroredCommit: mirror.LastMirroredCommit,
		CreatedAt:          mirror.CreatedAt.UTC().Format(time.RFC3339Nano),
		UpdatedAt:          mirror.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func toKBSourceMirrorCredentialSummary(summary *kb.SourceMirrorCredentialSummary) *dto.KBSourceMirrorCredentialSummary {
	if summary == nil {
		return nil
	}
	return &dto.KBSourceMirrorCredentialSummary{
		Kind:              summary.Kind,
		DisplayName:       summary.DisplayName,
		OwnerPrincipalID:  summary.OwnerPrincipalID,
		ProviderType:      summary.ProviderType,
		ProviderBaseURL:   summary.ProviderBaseURL,
		Status:            summary.Status,
		LastUsedAt:        optionalTime(summary.LastUsedAt),
		ExpiresAt:         optionalTime(summary.ExpiresAt),
		LinkedSourceCount: summary.LinkedSourceCount,
	}
}

func ToKBSourceMirrorSyncJob(job kb.Job) dto.KBSourceMirrorSyncJob {
	progress := kbservice.KBJobDisplayProgressForJob(job)
	diagnostics := kbservice.SourceMirrorJobDiagnosticsForJob(job)
	queueWaitMS := durationMilliseconds(&job.CreatedAt, job.StartedAt)
	return dto.KBSourceMirrorSyncJob{
		ID:                  job.ID,
		WorkspaceID:         job.WorkspaceID,
		KnowledgeBaseID:     job.KnowledgeBaseID,
		SourceID:            job.SourceID,
		Status:              job.Status,
		Stage:               progress.Stage,
		FailedStage:         diagnostics.FailedStage,
		Message:             progress.Message,
		Error:               kbservice.SanitizeKBProgressMessage(job.Error),
		ErrorCode:           diagnostics.ErrorCode,
		Retryable:           diagnostics.Retryable,
		Ref:                 diagnostics.Ref,
		Trigger:             diagnostics.Trigger,
		RequestedBy:         diagnostics.RequestedBy,
		TraceID:             diagnostics.TraceID,
		AttemptCount:        job.AttemptCount,
		GitOperation:        diagnostics.GitOperation,
		GitAttempt:          diagnostics.GitAttempt,
		GitMaxAttempts:      diagnostics.GitMaxAttempts,
		QueueWaitMS:         queueWaitMS,
		ExecutionDurationMS: durationMilliseconds(job.StartedAt, job.FinishedAt),
		CreatedAt:           job.CreatedAt.UTC().Format(time.RFC3339Nano),
		StartedAt:           optionalTime(job.StartedAt),
		UpdatedAt:           job.UpdatedAt.UTC().Format(time.RFC3339Nano),
		FinishedAt:          optionalTime(job.FinishedAt),
	}
}

func durationMilliseconds(start, end *time.Time) *int64 {
	if start == nil || end == nil || end.Before(*start) {
		return nil
	}
	duration := end.Sub(*start).Milliseconds()
	return &duration
}

func ToKBSourceMirrorSyncJobs(jobs []kb.Job) []dto.KBSourceMirrorSyncJob {
	if jobs == nil {
		return nil
	}
	out := make([]dto.KBSourceMirrorSyncJob, 0, len(jobs))
	for _, job := range jobs {
		out = append(out, ToKBSourceMirrorSyncJob(job))
	}
	return out
}

func ToKBSourceMirrorSyncListResponse(result kbservice.SourceMirrorSyncListResult) dto.KBSourceMirrorSyncListResponse {
	return dto.KBSourceMirrorSyncListResponse{Mirror: ToKBSourceMirrorSummary(result.Mirror), Items: ToKBSourceMirrorSyncJobs(result.Jobs)}
}

func ToKBSources(sources []kb.Source) []dto.KBSource {
	if sources == nil {
		return nil
	}
	out := make([]dto.KBSource, 0, len(sources))
	for _, source := range sources {
		out = append(out, ToKBSource(source))
	}
	return out
}

func ToKBRetrievalContextResponse(result kbservice.KBRetrievalContextResult) dto.KBRetrievalContextResponse {
	bases := make([]dto.KBRetrievalContextKnowledgeBase, 0, len(result.KnowledgeBases))
	for _, base := range result.KnowledgeBases {
		sources := make([]dto.KBRetrievalContextSource, 0, len(base.Sources))
		for _, source := range base.Sources {
			sources = append(sources, dto.KBRetrievalContextSource{
				SourceID:        source.SourceID,
				SourceKind:      source.SourceKind,
				RepoRole:        repositoryRoleForWire(source.RepoRole),
				ResourceType:    source.ResourceType,
				ResourceID:      source.ResourceID,
				IndexStatus:     source.IndexStatus,
				RepoOwner:       source.RepoOwner,
				RepoName:        source.RepoName,
				RepoFullName:    source.RepoFullName,
				Revision:        source.Revision,
				FreshnessStatus: source.FreshnessStatus,
			})
		}
		bases = append(bases, dto.KBRetrievalContextKnowledgeBase{
			ID:             base.ID,
			Name:           base.Name,
			Description:    base.Description,
			Sources:        sources,
			HasMoreSources: base.HasMoreSources,
		})
	}
	return dto.KBRetrievalContextResponse{KnowledgeBases: bases}
}

func ToKBRetrievalSearchResponse(result kbservice.KBSearchResult) dto.KBRetrievalSearchResponse {
	return dto.KBRetrievalSearchResponse{Citations: ToKBRetrievalCitations(result.Hits), Total: result.Total, HasMore: result.HasMore, Truncated: result.Truncated, Warnings: result.Warnings, Error: toKBStructuredError(result.Error)}
}

func ToKBRetrievalCitations(hits []kbservice.KBSearchHit) []dto.KBRetrievalCitation {
	if hits == nil {
		return nil
	}
	out := make([]dto.KBRetrievalCitation, 0, len(hits))
	for _, hit := range hits {
		out = append(out, dto.KBRetrievalCitation{CitationID: hit.CitationID, Title: hit.Title, Snippet: hit.Snippet, SourceLabel: converterFirstNonEmptyString(hit.KnowledgeBaseName, hit.RepoFullName), Score: hit.Score})
	}
	return out
}

func ToKBRetrievalFetchResponse(result kbservice.KBFetchResult) dto.KBRetrievalFetchResponse {
	out := dto.KBRetrievalFetchResponse{Items: make([]dto.KBRetrievalFetchItem, 0, len(result.Items)), Warnings: result.Warnings}
	for _, item := range result.Items {
		out.Items = append(out.Items, dto.KBRetrievalFetchItem{CitationID: item.CitationID, Title: item.Title, Content: item.Content, SourceLabel: item.SourceLabel, Truncated: item.Truncated, OriginalBytes: item.OriginalBytes, Error: toKBStructuredError(item.Error)})
	}
	return out
}

func converterFirstNonEmptyString(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func ToKBRetrievalReadResponse(page kbservice.KBReadResult) dto.KBRetrievalReadResponse {
	return dto.KBRetrievalReadResponse{
		KnowledgeBaseID:   page.KnowledgeBaseID,
		KnowledgeBaseName: page.KnowledgeBaseName,
		SourceID:          page.SourceID,
		SourceKind:        page.SourceKind,
		RepoRole:          repositoryRoleForWire(page.RepoRole),
		ResourceType:      page.ResourceType,
		ResourceID:        page.ResourceID,
		IndexStatus:       page.IndexStatus,
		RepoOwner:         page.RepoOwner,
		RepoName:          page.RepoName,
		RepoFullName:      page.RepoFullName,
		Slug:              page.Slug,
		Title:             page.Title,
		Body:              page.Body,
		Format:            page.Format,
		Truncated:         page.Truncated,
		OriginalBytes:     page.OriginalBytes,
		SHA:               page.SHA,
		Revision:          page.Revision,
		FreshnessStatus:   page.FreshnessStatus,
		CitationURI:       page.CitationURI,
		Route:             ToKBCitationRoute(page.Route),
		Error:             toKBStructuredError(page.Error),
	}
}

func ToKBRetrievalTreeResponse(result kbservice.KBTreeResult) dto.KBRetrievalTreeResponse {
	entries := make([]dto.KBRetrievalTreeEntry, 0, len(result.Entries))
	for _, entry := range result.Entries {
		entries = append(entries, dto.KBRetrievalTreeEntry{
			KnowledgeBaseID:   entry.KnowledgeBaseID,
			KnowledgeBaseName: entry.KnowledgeBaseName,
			SourceID:          entry.SourceID,
			SourceKind:        entry.SourceKind,
			RepoRole:          repositoryRoleForWire(entry.RepoRole),
			ResourceType:      entry.ResourceType,
			ResourceID:        entry.ResourceID,
			IndexStatus:       entry.IndexStatus,
			RepoOwner:         entry.RepoOwner,
			RepoName:          entry.RepoName,
			RepoFullName:      entry.RepoFullName,
			Path:              entry.Path,
			Name:              entry.Name,
			Kind:              entry.Kind,
			Slug:              entry.Slug,
			Title:             entry.Title,
			IsDir:             entry.IsDir,
			SHA:               entry.SHA,
			Revision:          entry.Revision,
			FreshnessStatus:   entry.FreshnessStatus,
			Size:              entry.Size,
			CitationURI:       entry.CitationURI,
			Route:             ToKBCitationRoute(entry.Route),
		})
	}
	return dto.KBRetrievalTreeResponse{Entries: entries, Total: result.Total, HasMore: result.HasMore, NextCursor: result.NextCursor, Error: toKBStructuredError(result.Error)}
}

func ToKBCapabilitiesResponse(result kbservice.KBCapabilitiesResult) dto.KBCapabilitiesResponse {
	bases := make([]dto.KBCapabilityKnowledgeBase, 0, len(result.KnowledgeBases))
	for _, base := range result.KnowledgeBases {
		sources := make([]dto.KBCapabilitySource, 0, len(base.Sources))
		for _, source := range base.Sources {
			sources = append(sources, dto.KBCapabilitySource{
				SourceID:        source.SourceID,
				SourceKind:      source.SourceKind,
				RepoRole:        repositoryRoleForWire(source.RepoRole),
				ResourceType:    source.ResourceType,
				ResourceID:      source.ResourceID,
				IndexStatus:     source.IndexStatus,
				RepoOwner:       source.RepoOwner,
				RepoName:        source.RepoName,
				RepoFullName:    source.RepoFullName,
				Revision:        source.Revision,
				FreshnessStatus: source.FreshnessStatus,
				Operations:      toKBCapabilityOperations(source.Operations),
			})
		}
		bases = append(bases, dto.KBCapabilityKnowledgeBase{
			ID:          base.ID,
			Name:        base.Name,
			Description: base.Description,
			Sources:     sources,
			Operations:  toKBCapabilityOperations(base.Operations),
		})
	}
	return dto.KBCapabilitiesResponse{KnowledgeBases: bases}
}

func toKBCapabilityOperations(raw map[string]kbservice.KBCapabilityOperation) map[string]dto.KBCapabilityOperation {
	out := make(map[string]dto.KBCapabilityOperation, len(raw))
	for key, op := range raw {
		out[key] = dto.KBCapabilityOperation{
			Supported:       op.Supported,
			ReadOnly:        op.ReadOnly,
			Writable:        op.Writable,
			BackendWritable: op.BackendWritable,
			MCPToolExposed:  op.MCPToolExposed,
			TargetType:      op.TargetType,
			Limitations:     append([]string(nil), op.Limitations...),
			PreferredTool:   op.PreferredTool,
		}
	}
	return out
}

func ToKBManifestResponse(result kbservice.KBManifestResult) dto.KBManifestResponse {
	bases := make([]dto.KBManifestKnowledgeBase, 0, len(result.KnowledgeBases))
	for _, base := range result.KnowledgeBases {
		sources := make([]dto.KBManifestSource, 0, len(base.Sources))
		for _, source := range base.Sources {
			sources = append(sources, dto.KBManifestSource{
				SourceID:        source.SourceID,
				SourceKind:      source.SourceKind,
				RepoRole:        repositoryRoleForWire(source.RepoRole),
				ResourceType:    source.ResourceType,
				ResourceID:      source.ResourceID,
				IndexStatus:     source.IndexStatus,
				RepoOwner:       source.RepoOwner,
				RepoName:        source.RepoName,
				RepoFullName:    source.RepoFullName,
				Revision:        source.Revision,
				FreshnessStatus: source.FreshnessStatus,
				Entries:         ToKBRetrievalTreeResponse(kbservice.KBTreeResult{Entries: source.Entries}).Entries,
				PageCount:       source.PageCount,
				DirCount:        source.DirCount,
				HasMore:         source.HasMore,
				NextCursor:      source.NextCursor,
				Error:           toKBStructuredError(source.Error),
			})
		}
		bases = append(bases, dto.KBManifestKnowledgeBase{ID: base.ID, Name: base.Name, Description: base.Description, Sources: sources})
	}
	return dto.KBManifestResponse{KnowledgeBases: bases, TotalSources: result.TotalSources, TotalPages: result.TotalPages, HasMore: result.HasMore}
}

func ToKBStatResponse(result kbservice.KBStatResult) dto.KBStatResponse {
	return dto.KBStatResponse{
		Exists:            result.Exists,
		KnowledgeBaseID:   result.KnowledgeBaseID,
		KnowledgeBaseName: result.KnowledgeBaseName,
		SourceID:          result.SourceID,
		SourceKind:        result.SourceKind,
		RepoRole:          repositoryRoleForWire(result.RepoRole),
		ResourceType:      result.ResourceType,
		ResourceID:        result.ResourceID,
		IndexStatus:       result.IndexStatus,
		RepoOwner:         result.RepoOwner,
		RepoName:          result.RepoName,
		RepoFullName:      result.RepoFullName,
		Slug:              result.Slug,
		Title:             result.Title,
		SHA:               result.SHA,
		Revision:          result.Revision,
		FreshnessStatus:   result.FreshnessStatus,
		CitationURI:       result.CitationURI,
		Route:             ToKBCitationRoute(result.Route),
		Error:             toKBStructuredError(result.Error),
	}
}

func ToKBBatchReadResponse(result kbservice.KBBatchReadResult) dto.KBBatchReadResponse {
	out := dto.KBBatchReadResponse{Results: make([]dto.KBBatchReadItem, 0, len(result.Results))}
	for _, item := range result.Results {
		var page *dto.KBRetrievalReadResponse
		if item.Page != nil {
			converted := ToKBRetrievalReadResponse(*item.Page)
			page = &converted
		}
		out.Results = append(out.Results, dto.KBBatchReadItem{Index: item.Index, Page: page, Error: toKBStructuredError(item.Error)})
	}
	return out
}

func ToKBBatchTreeResponse(result kbservice.KBBatchTreeResult) dto.KBBatchTreeResponse {
	out := dto.KBBatchTreeResponse{Results: make([]dto.KBBatchTreeItem, 0, len(result.Results))}
	for _, item := range result.Results {
		var tree *dto.KBRetrievalTreeResponse
		if item.Tree != nil {
			converted := ToKBRetrievalTreeResponse(*item.Tree)
			tree = &converted
		}
		out.Results = append(out.Results, dto.KBBatchTreeItem{Index: item.Index, Tree: tree, Error: toKBStructuredError(item.Error)})
	}
	return out
}

func toKBStructuredError(err *kbservice.KBStructuredError) *dto.KBStructuredError {
	if err == nil {
		return nil
	}
	return &dto.KBStructuredError{Code: err.Code, Message: err.Message, Status: err.Status}
}

func ToKBCitationRoute(route kbservice.KBCitationRoute) dto.KBCitationRoute {
	return dto.KBCitationRoute{
		KnowledgeBaseID: route.KnowledgeBaseID,
		SourceID:        route.SourceID,
		RepoID:          route.RepoID,
		RepoRole:        repositoryRoleForWire(route.RepoRole),
		Kind:            route.Kind,
		Path:            route.Path,
		Slug:            route.Slug,
		Revision:        route.Revision,
	}
}

func ToKBAuditEvent(event kb.AuditEvent) dto.KBAuditEvent {
	return dto.KBAuditEvent{ID: event.ID, WorkspaceID: event.WorkspaceID, KnowledgeBaseID: event.KnowledgeBaseID, ActorID: event.ActorID, Action: event.Action, ResourceType: event.ResourceType, ResourceID: event.ResourceID, Summary: event.Summary, Details: event.Details, CreatedAt: event.CreatedAt.UTC().Format(time.RFC3339Nano)}
}

func ToKBAuditEvents(events []kb.AuditEvent) []dto.KBAuditEvent {
	if events == nil {
		return nil
	}
	out := make([]dto.KBAuditEvent, 0, len(events))
	for _, event := range events {
		out = append(out, ToKBAuditEvent(event))
	}
	return out
}

func ToKBSavedView(view kb.KBSavedView) dto.KBSavedView {
	return dto.KBSavedView{
		ID:               view.ID,
		WorkspaceID:      view.WorkspaceID,
		OwnerPrincipalID: view.OwnerPrincipalID,
		Name:             view.Name,
		Slug:             view.Slug,
		Description:      view.Description,
		Filter:           view.Filter,
		Visibility:       view.Visibility,
		CreatedAt:        view.CreatedAt.UTC().Format(timeLayout),
		UpdatedAt:        view.UpdatedAt.UTC().Format(timeLayout),
	}
}

func ToKBSavedViews(views []kb.KBSavedView) []dto.KBSavedView {
	if views == nil {
		return nil
	}
	out := make([]dto.KBSavedView, 0, len(views))
	for _, view := range views {
		out = append(out, ToKBSavedView(view))
	}
	return out
}

func ToKBOverview(result kbservice.KBOverview) dto.KBOverviewResponse {
	return dto.KBOverviewResponse{
		KnowledgeBaseID:  result.KnowledgeBaseID,
		Health:           toKBOverviewHealth(result.Health),
		Activity:         toKBOverviewActivities(result.Activity),
		Alerts:           toKBOverviewAlerts(result.Alerts),
		RecommendedPaths: toKBOverviewRecommendedPaths(result.RecommendedPaths),
		Sources:          toKBOverviewSources(result.Sources),
	}
}

func ToKBSourceDetail(result kbservice.KBSourceDetail) dto.KBSourceDetailResponse {
	return dto.KBSourceDetailResponse{
		KnowledgeBaseID:  result.KnowledgeBaseID,
		Source:           ToKBSource(result.Source),
		Health:           toKBOverviewHealth(result.Health),
		Activity:         toKBOverviewActivities(result.Activity),
		Alerts:           toKBOverviewAlerts(result.Alerts),
		RecommendedPaths: toKBOverviewRecommendedPaths(result.RecommendedPaths),
		Repositories:     toKBSourceDetailRepositories(result.Repositories, result.RepositoryAccess),
		CurrentRevision:  toKBSourceRevision(result.CurrentRevision),
		AnalysisRuns:     toKBSourceAnalysisRuns(result.AnalysisRuns),
		Jobs:             toKBSourceJobs(result.Jobs),
	}
}

func toKBOverviewHealth(health kbservice.KBOverviewHealth) dto.KBOverviewHealth {
	return dto.KBOverviewHealth{Status: health.Status, TotalSources: health.TotalSources, ReadySources: health.ReadySources, AttentionCount: health.AttentionCount, StaleSources: health.StaleSources, FailedSources: health.FailedSources, PendingSources: health.PendingSources}
}

func toKBOverviewActivities(items []kbservice.KBOverviewActivity) []dto.KBOverviewActivity {
	out := make([]dto.KBOverviewActivity, 0, len(items))
	for _, item := range items {
		out = append(out, dto.KBOverviewActivity{ID: item.ID, Kind: item.Kind, ActorID: item.ActorID, Action: item.Action, ResourceType: item.ResourceType, ResourceID: item.ResourceID, Status: item.Status, Summary: item.Summary, CreatedAt: item.CreatedAt.UTC().Format(time.RFC3339Nano)})
	}
	return out
}

func toKBOverviewAlerts(items []kbservice.KBOverviewAlert) []dto.KBOverviewAlert {
	out := make([]dto.KBOverviewAlert, 0, len(items))
	for _, item := range items {
		out = append(out, dto.KBOverviewAlert{ID: item.ID, Severity: item.Severity, Kind: item.Kind, SourceID: item.SourceID, Message: item.Message, CreatedAt: item.CreatedAt.UTC().Format(time.RFC3339Nano)})
	}
	return out
}

func toKBOverviewRecommendedPaths(items []kbservice.KBOverviewRecommendedPath) []dto.KBOverviewRecommendedPath {
	out := make([]dto.KBOverviewRecommendedPath, 0, len(items))
	for _, item := range items {
		out = append(out, dto.KBOverviewRecommendedPath{ID: item.ID, Label: item.Label, Reason: item.Reason, Href: item.Href, SourceID: item.SourceID})
	}
	return out
}

func toKBOverviewSources(items []kbservice.KBOverviewSource) []dto.KBOverviewSource {
	out := make([]dto.KBOverviewSource, 0, len(items))
	for _, item := range items {
		out = append(out, dto.KBOverviewSource{
			SourceID:                        item.SourceID,
			Name:                            item.Name,
			SourceType:                      item.SourceType,
			Status:                          item.Status,
			SyncStatus:                      item.SyncStatus,
			IndexStatus:                     item.IndexStatus,
			AnalysisStatus:                  item.AnalysisStatus,
			FreshnessStatus:                 item.FreshnessStatus,
			RepoFullName:                    item.RepoFullName,
			SourceRepositoryAccessProfile:   item.SourceRepositoryAccessProfile,
			SourceRepositoryAccessStatus:    item.SourceRepositoryAccessStatus,
			InsightsRepositoryAccessProfile: item.InsightsRepositoryAccessProfile,
			InsightsRepositoryAccessStatus:  item.InsightsRepositoryAccessStatus,
			Revision:                        item.Revision,
			UpdatedAt:                       item.UpdatedAt.UTC().Format(time.RFC3339Nano),
			ImportProgress:                  toKBSourceImportProgress(item.ImportProgress),
		})
	}
	return out
}

func toKBSourceDetailRepositories(repos kbservice.KBSourceDetailRepositories, accessByRole map[string]kbservice.KBPermissionRepositoryAccess) dto.KBSourceDetailRepositories {
	return dto.KBSourceDetailRepositories{
		Primary:  toResourceRepositoryRefWithAccess(repos.Primary, sourceRepositoryAccessForRole(accessByRole, repos.Primary)),
		Mirror:   toResourceRepositoryRefWithAccess(repos.Mirror, sourceRepositoryAccessForRole(accessByRole, repos.Mirror)),
		Insights: toResourceRepositoryRefWithAccess(repos.Insights, sourceRepositoryAccessForRole(accessByRole, repos.Insights)),
	}
}

func ToResourceRepositoryRef(repo *repos.ResourceRepository) *dto.ResourceRepositoryRef {
	if repo == nil {
		return nil
	}
	return &dto.ResourceRepositoryRef{
		ID:           repo.ID,
		RepoRole:     repositoryRoleForWire(repo.RepoRole),
		RepoOwner:    repo.RepoOwner,
		RepoName:     repo.RepoName,
		RepoFullName: repo.RepoFullName,
		RepoID:       repo.AGSRepoID,
		DefaultRef:   repo.DefaultRef,
		UpdatedAt:    repo.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}
}

func toResourceRepositoryRefWithAccess(repo *repos.ResourceRepository, access *kbservice.KBPermissionRepositoryAccess) *dto.ResourceRepositoryRef {
	ref := ToResourceRepositoryRef(repo)
	if ref == nil {
		return nil
	}
	if access != nil {
		ref.RepositoryAccessProfile = access.EffectiveRepositoryAccessProfile
		ref.RepositoryAccessStatus = access.RepositoryAccessStatus
		ref.RepositoryAccessLastError = access.RepositoryAccessLastError
	}
	return ref
}

func sourceRepositoryAccessForRole(accessByRole map[string]kbservice.KBPermissionRepositoryAccess, repo *repos.ResourceRepository) *kbservice.KBPermissionRepositoryAccess {
	if repo == nil || len(accessByRole) == 0 {
		return nil
	}
	access, ok := accessByRole[repo.RepoRole]
	if !ok {
		return nil
	}
	return &access
}

func toKBSourceRevision(revision *kb.SourceRevision) *dto.KBSourceRevision {
	if revision == nil {
		return nil
	}
	return &dto.KBSourceRevision{ID: revision.ID, SourceCommitSHA: revision.SourceCommitSHA, InsightsCommitSHA: revision.InsightsCommitSHA, PublishedWikiRevision: revision.PublishedWikiRevision, FreshnessStatus: revision.FreshnessStatus, UpdatedAt: revision.UpdatedAt.UTC().Format(time.RFC3339Nano)}
}

func toKBSourceAnalysisRuns(items []kb.AnalysisRun) []dto.KBSourceAnalysisRun {
	out := make([]dto.KBSourceAnalysisRun, 0, len(items))
	for _, item := range items {
		progress := kbservice.KBAnalysisRunDisplayProgress(item)
		message := progress.Message
		if strings.TrimSpace(item.Status) == kbservice.KBJobStatusFailed {
			message = ""
		}
		out = append(out, dto.KBSourceAnalysisRun{ID: item.ID, Trigger: item.Trigger, Status: item.Status, InputKind: item.InputKind, OutputRepoRole: repositoryRoleForWire(item.OutputRepoRole), OutputCommitSHA: item.OutputCommitSHA, PublishedWikiRevision: item.PublishedWikiRevision, Stage: progress.Stage, Message: message, PageCount: progress.PageCount, FinishedAt: optionalTime(item.CompletedAt), CreatedAt: item.CreatedAt.UTC().Format(time.RFC3339Nano), UpdatedAt: item.UpdatedAt.UTC().Format(time.RFC3339Nano)})
	}
	return out
}

func toKBSourceJobs(items []kb.Job) []dto.KBSourceJob {
	out := make([]dto.KBSourceJob, 0, len(items))
	for _, item := range items {
		progress := kbservice.KBJobDisplayProgressForJob(item)
		message := progress.Message
		if strings.TrimSpace(item.Status) == kbservice.KBJobStatusFailed {
			message = ""
		}
		out = append(out, dto.KBSourceJob{ID: item.ID, JobType: item.JobType, Status: item.Status, Stage: progress.Stage, Message: message, PageCount: progress.PageCount, FinishedAt: optionalTime(item.FinishedAt), CreatedAt: item.CreatedAt.UTC().Format(time.RFC3339Nano), UpdatedAt: item.UpdatedAt.UTC().Format(time.RFC3339Nano)})
	}
	return out
}
