package converter

import (
	"encoding/json"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/store/taskboard"
)

const (
	taskActivityCompatSourceKindActivity = "activity"
	taskActivityCompatSourceKindMessage  = "message"
)

func ToMessagePresentation(presentation *domain.MessagePresentation) *domain.MessagePresentation {
	if presentation == nil || presentation.Validate() != nil {
		return nil
	}
	copy := *presentation
	copy.Args = append(json.RawMessage(nil), presentation.Args...)
	return &copy
}

// FromTaskCreateInput converts an API task create request payload to a taskboard input.
func FromTaskCreateInput(input *dto.TaskCreateInputRequest) *taskboard.TaskCreateInput {
	if input == nil {
		return nil
	}
	return &taskboard.TaskCreateInput{
		Title:            input.Title,
		Description:      input.Description,
		Kind:             input.Kind,
		Priority:         input.Priority,
		KindOptionID:     input.KindOptionID,
		PriorityOptionID: input.PriorityOptionID,
		AssigneeRef:      input.AssigneeRef,
	}
}

// FromAgentTaskWrite converts a canonical create task item to a taskboard input.
func FromAgentTaskWrite(input *dto.AgentTaskWrite) *taskboard.TaskCreateInput {
	if input == nil {
		return nil
	}
	return &taskboard.TaskCreateInput{
		Title:       input.Title,
		Description: input.Description,
		Kind:        input.Kind,
		Priority:    input.Priority,
		AssigneeRef: input.Assignee,
	}
}

// FromTaskBrowseQuery converts an API task browse request to a taskboard query.
func FromTaskBrowseQuery(query dto.TaskBrowseRequest) (taskboard.TaskBrowseQuery, error) {
	cursor, err := FromTaskBrowseCursorToken(query.Cursor, query.SortMode)
	if err != nil {
		return taskboard.TaskBrowseQuery{}, err
	}
	columnCursors, err := FromTaskBrowseColumnCursorTokens(query.ColumnCursors, query.SortMode)
	if err != nil {
		return taskboard.TaskBrowseQuery{}, err
	}
	return taskboard.TaskBrowseQuery{
		Limit:             query.Limit,
		SearchQuery:       query.SearchQuery,
		SortMode:          query.SortMode,
		StatusFilter:      query.StatusFilter,
		QuickFilters:      query.QuickFilters,
		StructuredFilters: query.StructuredFilters,
		IncludeSubtasks:   query.IncludeSubtasks,
		Cursor:            cursor,
		ColumnCursors:     columnCursors,
	}, nil
}

// FromTaskBoardViewState converts an API view-state payload to taskboard view-state.
func FromTaskBoardViewState(view dto.TaskBoardViewStateRequest) taskboard.TaskBoardViewState {
	return taskboard.TaskBoardViewState{
		View:                view.View,
		SearchQuery:         view.SearchQuery,
		QuickFilters:        view.QuickFilters,
		StructuredFilters:   view.StructuredFilters,
		SortMode:            view.SortMode,
		CardPropertyToggles: view.CardPropertyToggles,
		DoneCollapsed:       view.DoneCollapsed,
	}
}

// FromTaskChecklistItems converts API checklist items to taskboard checklist items.
// It preserves nil-vs-empty semantics: nil input returns nil, empty non-nil input returns empty non-nil output.
func FromTaskChecklistItems(items []dto.TaskChecklistItem) []taskboard.TaskChecklistItem {
	if items == nil {
		return nil
	}
	converted := make([]taskboard.TaskChecklistItem, 0, len(items))
	for _, item := range items {
		converted = append(converted, taskboard.TaskChecklistItem{ID: item.ID, Text: item.Text, Checked: item.Checked})
	}
	return converted
}

// ToTask converts a taskboard task to an API task DTO.
func ToTask(task taskboard.Task) dto.Task {
	return dto.Task{
		ID:                 task.ID,
		TaskScopeID:        task.TaskScopeID,
		ChannelID:          task.ChannelID,
		TaskNumber:         task.TaskNumber,
		Title:              task.Title,
		Status:             task.Status,
		Description:        task.Description,
		Kind:               task.Kind,
		Priority:           task.Priority,
		BlockedReason:      task.BlockedReason,
		LastActivityAt:     task.LastActivityAt,
		ArtifactCount:      task.ArtifactCount,
		ParentTaskRef:      task.ParentTaskRef,
		DiscussionCount:    task.DiscussionCount,
		AssigneeProfile:    ToTaskPrincipalProfile(task.AssigneeProfile),
		KindField:          ToTaskFieldValue(task.KindField),
		PriorityField:      ToTaskFieldValue(task.PriorityField),
		CheckpointProgress: dto.TaskProgressSummary{Total: task.CheckpointProgress.Total, Done: task.CheckpointProgress.Done},
		SubtaskProgress:    dto.TaskProgressSummary{Total: task.SubtaskProgress.Total, Done: task.SubtaskProgress.Done},
		CreatedAt:          task.CreatedAt,
		CreatedByProfile:   ToTaskPrincipalProfile(task.CreatedByProfile),
		CreatedByType:      task.CreatedByType,
		CreatedByID:        task.CreatedByID,
		CreatedByName:      task.CreatedByName,
		Source:             ToContributionSource(task.Source),
		SourceThread:       ToTaskSourceThread(task.SourceThread),
		ReadState:          ToTaskReadState(task.ReadState),
		AssignedAt:         task.AssignedAt,
		CompletedAt:        task.CompletedAt,
	}
}

func ToTaskPrincipalProfile(profile *taskboard.TaskPrincipalProfile) *dto.AuthorProfile {
	if profile == nil {
		return nil
	}
	return ToAuthorProfile(*profile)
}

// ToTasks converts taskboard tasks to API task DTOs. Empty input returns nil,
// matching legacy agent/nested presenter behavior.
func ToTasks(tasks []taskboard.Task) []dto.Task {
	if len(tasks) == 0 {
		return nil
	}
	items := make([]dto.Task, 0, len(tasks))
	for _, task := range tasks {
		items = append(items, ToTask(task))
	}
	return items
}

// ToTasksEmpty converts taskboard tasks to API task DTOs. Empty input returns
// an empty non-nil slice, matching legacy browser task list behavior.
func ToTasksEmpty(tasks []taskboard.Task) []dto.Task {
	if len(tasks) == 0 {
		return []dto.Task{}
	}
	return ToTasks(tasks)
}

func ToTaskFieldValue(value *taskboard.TaskFieldValue) *dto.TaskFieldValue {
	if value == nil {
		return nil
	}
	return &dto.TaskFieldValue{OptionID: value.OptionID, Key: value.Key, Label: value.Label, RawValue: value.RawValue, IsLegacy: value.IsLegacy, IsNone: value.IsNone}
}

func ToTaskSourceThread(thread *taskboard.TaskSourceThread) *dto.TaskSourceThread {
	if thread == nil {
		return nil
	}
	return &dto.TaskSourceThread{ThreadChannelID: thread.ThreadChannelID, ParentChannelID: thread.ParentChannelID, ParentMessageID: thread.ParentMessageID, Ref: thread.Ref}
}

func ToTaskReadState(readState *taskboard.TaskReadState) *dto.TaskReadState {
	if readState == nil {
		return nil
	}
	return &dto.TaskReadState{
		UnreadCount:            readState.UnreadCount,
		ManualUnread:           readState.ManualUnread,
		LatestEventID:          readState.LatestEventID,
		LatestEventCreatedAt:   readState.LatestEventCreatedAt,
		LastReadEventID:        readState.LastReadEventID,
		LastReadEventCreatedAt: readState.LastReadEventCreatedAt,
		WatchState:             readState.WatchState,
	}
}

func ToTaskFieldOption(option taskboard.TaskFieldOption) dto.TaskFieldOption {
	return dto.TaskFieldOption{
		ID:         option.ID,
		FieldKind:  option.FieldKind,
		Key:        option.Key,
		Label:      option.Label,
		Aliases:    option.Aliases,
		Position:   option.Position,
		IsActive:   option.IsActive,
		UsageCount: option.UsageCount,
	}
}

func ToTaskFieldOptions(options []taskboard.TaskFieldOption) []dto.TaskFieldOption {
	if len(options) == 0 {
		return nil
	}
	items := make([]dto.TaskFieldOption, 0, len(options))
	for _, option := range options {
		items = append(items, ToTaskFieldOption(option))
	}
	return items
}

func ToTaskBoardOptions(response taskboard.ListTaskFieldOptionsResponse) dto.TaskBoardOptionsResponse {
	return dto.TaskBoardOptionsResponse{
		CanManage:       response.CanManage,
		PriorityOptions: ToTaskFieldOptions(response.PriorityOptions),
		KindOptions:     ToTaskFieldOptions(response.KindOptions),
	}
}

func ToTaskBoardOptionActive(response taskboard.SetTaskFieldOptionActiveResponse) dto.TaskBoardOptionActiveResponse {
	return dto.TaskBoardOptionActiveResponse{Option: ToTaskFieldOption(response.Option), AffectedTasks: ToTasks(response.AffectedTasks)}
}

func ToTaskBoardViewState(state taskboard.TaskBoardViewState) dto.TaskBoardViewStateRequest {
	return dto.TaskBoardViewStateRequest{
		View:                state.View,
		SearchQuery:         state.SearchQuery,
		QuickFilters:        state.QuickFilters,
		StructuredFilters:   state.StructuredFilters,
		SortMode:            state.SortMode,
		CardPropertyToggles: state.CardPropertyToggles,
		DoneCollapsed:       state.DoneCollapsed,
	}
}

func ToTaskBoardViewStateResponse(response taskboard.GetTaskBoardViewStateResponse) dto.TaskBoardViewStateResponse {
	return dto.TaskBoardViewStateResponse{ViewState: ToTaskBoardViewState(response.ViewState)}
}

func ToTaskBoardViewStatePutResponse(response taskboard.PutTaskBoardViewStateResponse) dto.TaskBoardViewStatePutResponse {
	return dto.TaskBoardViewStatePutResponse{ViewState: ToTaskBoardViewState(response.ViewState)}
}

func ToTaskBoardSavedView(savedView taskboard.TaskBoardSavedView) dto.TaskBoardSavedView {
	return dto.TaskBoardSavedView{
		ID:        savedView.ID,
		Name:      savedView.Name,
		ViewState: ToTaskBoardViewState(savedView.ViewState),
		CreatedAt: savedView.CreatedAt,
		UpdatedAt: savedView.UpdatedAt,
	}
}

func ToTaskBoardSavedViews(savedViews []taskboard.TaskBoardSavedView) []dto.TaskBoardSavedView {
	if len(savedViews) == 0 {
		return nil
	}
	out := make([]dto.TaskBoardSavedView, 0, len(savedViews))
	for _, savedView := range savedViews {
		out = append(out, ToTaskBoardSavedView(savedView))
	}
	return out
}

func ToTaskBoardSavedViewsResponse(response taskboard.ListTaskBoardSavedViewsResponse) dto.TaskBoardSavedViewsResponse {
	return dto.TaskBoardSavedViewsResponse{SavedViews: ToTaskBoardSavedViews(response.SavedViews)}
}

func ToTaskBoardSavedViewCreateResponse(response taskboard.CreateTaskBoardSavedViewResponse) dto.TaskBoardSavedViewResponse {
	return dto.TaskBoardSavedViewResponse{SavedView: ToTaskBoardSavedView(response.SavedView)}
}

func ToTaskBoardSavedViewUpdateResponse(response taskboard.UpdateTaskBoardSavedViewResponse) dto.TaskBoardSavedViewResponse {
	return dto.TaskBoardSavedViewResponse{SavedView: ToTaskBoardSavedView(response.SavedView)}
}

func ToTaskBrowseQuery(query taskboard.TaskBrowseQuery) dto.TaskBrowseRequest {
	return dto.TaskBrowseRequest{
		Limit:             query.Limit,
		SearchQuery:       query.SearchQuery,
		SortMode:          query.SortMode,
		StatusFilter:      query.StatusFilter,
		QuickFilters:      query.QuickFilters,
		StructuredFilters: query.StructuredFilters,
		IncludeSubtasks:   query.IncludeSubtasks,
		Cursor:            ToTaskBrowseCursorToken(query.Cursor),
		ColumnCursors:     ToTaskBrowseColumnCursorTokens(query.ColumnCursors),
	}
}

func ToTaskBrowseResponse(response taskboard.TaskRowBrowsePage) dto.TaskBrowseResponse {
	return dto.TaskBrowseResponse{
		QueryEcho:    ToTaskBrowseQuery(response.QueryEcho),
		Rows:         ToTasksEmpty(response.Rows),
		TotalCount:   response.TotalCount,
		HasMore:      response.HasMore,
		NextCursor:   ToTaskBrowseCursorToken(response.NextCursor),
		ColumnCounts: response.ColumnCounts,
	}
}

func ToRelatedTaskSignal(signal *taskboard.RelatedTaskSignal) *dto.RelatedTaskSignal {
	if signal == nil {
		return nil
	}
	return &dto.RelatedTaskSignal{Type: signal.Type, Payload: signal.Payload, CreatedAt: signal.CreatedAt}
}

func ToRelatedTaskUnread(unread taskboard.RelatedTaskUnread) dto.RelatedTaskUnread {
	return dto.RelatedTaskUnread{Count: unread.Count, ManualUnread: unread.ManualUnread}
}

func ToRelatedTaskRow(row taskboard.RelatedTaskRow) dto.RelatedTaskRow {
	return dto.RelatedTaskRow{
		TaskID:          row.TaskID,
		TaskScopeID:     row.TaskScopeID,
		ChannelID:       row.ChannelID,
		TaskNumber:      row.TaskNumber,
		Title:           row.Title,
		Status:          row.Status,
		ParentTaskID:    row.ParentTaskID,
		ParentTaskRef:   row.ParentTaskRef,
		Source:          ToContributionSource(row.Source),
		SourceThread:    ToTaskSourceThread(row.SourceThread),
		AssigneeProfile: ToTaskPrincipalProfile(row.AssigneeProfile),
		LastSignal:      ToRelatedTaskSignal(row.LastSignal),
		Unread:          ToRelatedTaskUnread(row.Unread),
		Route:           row.Route,
	}
}

func ToRelatedTaskRows(rows []taskboard.RelatedTaskRow) []dto.RelatedTaskRow {
	if len(rows) == 0 {
		return []dto.RelatedTaskRow{}
	}
	items := make([]dto.RelatedTaskRow, 0, len(rows))
	for _, row := range rows {
		items = append(items, ToRelatedTaskRow(row))
	}
	return items
}

func ToRelatedTasksResponse(response taskboard.RelatedTasksResponse) dto.RelatedTasksResponse {
	return dto.RelatedTasksResponse{Tasks: ToRelatedTaskRows(response.Rows), HasMore: response.HasMore, NextCursor: ToRelatedTaskCursorToken(response.NextCursor)}
}

func ToTaskBoardColumnPage(page taskboard.TaskBoardColumnPage) dto.TaskBoardColumnPage {
	return dto.TaskBoardColumnPage{Tasks: ToTasksEmpty(page.Tasks), Count: page.Count, HasMore: page.HasMore, NextCursor: ToTaskBrowseCursorToken(page.NextCursor)}
}

func ToTaskBoardColumns(columns map[string]taskboard.TaskBoardColumnPage) map[string]dto.TaskBoardColumnPage {
	if len(columns) == 0 {
		return nil
	}
	items := make(map[string]dto.TaskBoardColumnPage, len(columns))
	for key, page := range columns {
		items[key] = ToTaskBoardColumnPage(page)
	}
	return items
}

func ToTaskBoardColumnDefinitions(definitions []taskboard.TaskBoardColumnDefinition) []dto.TaskBoardColumnDefinition {
	if len(definitions) == 0 {
		return nil
	}
	items := make([]dto.TaskBoardColumnDefinition, 0, len(definitions))
	for _, definition := range definitions {
		items = append(items, dto.TaskBoardColumnDefinition{
			Key:    definition.Key,
			Name:   definition.Name,
			Source: definition.Source,
		})
	}
	return items
}

func ToTaskBoardBrowseResponse(response taskboard.TaskBoardBrowsePage) dto.TaskBoardBrowseResponse {
	return dto.TaskBoardBrowseResponse{
		QueryEcho:         ToTaskBrowseQuery(response.QueryEcho),
		TotalCount:        response.TotalCount,
		ColumnOrder:       append([]string(nil), response.ColumnOrder...),
		ColumnDefinitions: ToTaskBoardColumnDefinitions(response.ColumnDefinitions),
		ColumnCounts:      response.ColumnCounts,
		Columns:           ToTaskBoardColumns(response.Columns),
	}
}

func ToTaskChecklistItems(items []taskboard.TaskChecklistItem) []dto.TaskChecklistItem {
	if len(items) == 0 {
		return nil
	}
	converted := make([]dto.TaskChecklistItem, 0, len(items))
	for _, item := range items {
		converted = append(converted, dto.TaskChecklistItem{ID: item.ID, Text: item.Text, Checked: item.Checked})
	}
	return converted
}

func ToTaskDiscussionEntry(entry taskboard.TaskDiscussionEntry) dto.TaskDiscussionEntry {
	authorProfile := ToAuthorProfile(entry.AuthorProfile)
	return dto.TaskDiscussionEntry{
		ID:                  entry.ID,
		TaskID:              entry.TaskID,
		Body:                entry.Body,
		AuthorProfile:       authorProfile,
		Source:              ToContributionSource(entry.Source),
		CreatedBy:           taskDiscussionCompatActorFromAuthorProfile(authorProfile, entry.CreatedBy),
		ResolvedMentions:    ToResolvedMentions(entry.ResolvedMentions),
		Reactions:           ToMessageReactionAggregates(entry.Reactions),
		Attachments:         ToAppAttachments(entry.Attachments),
		CreatedAt:           entry.CreatedAt,
		UpdatedAt:           entry.UpdatedAt,
		EditedAt:            entry.EditedAt,
		EditedByMemberID:    entry.EditedByMemberID,
		EditRevision:        entry.EditRevision,
		MessagePresentation: ToMessagePresentation(entry.MessagePresentation),
	}
}

// taskDiscussionCompatActorFromAuthorProfile derives the retained CreatedBy
// compatibility field from the canonical message AuthorProfile at the DTO
// boundary. The task discussion read model must not make TaskActor the source
// of truth for browser/API discussion authors.
func taskDiscussionCompatActorFromAuthorProfile(profile *dto.AuthorProfile, fallback taskboard.TaskActor) dto.TaskActor {
	return taskCompatActorFromAuthorProfile(profile, fallback)
}

func taskCompatActorFromAuthorProfile(profile *dto.AuthorProfile, fallback taskboard.TaskActor) dto.TaskActor {
	if profile == nil {
		return dto.TaskActor{Type: fallback.Type, ID: fallback.ID, Name: fallback.Name}
	}
	actorType := profile.ActorType
	if actorType == "service" {
		actorType = "app"
	}
	if actorType == "" {
		actorType = fallback.Type
	}
	return dto.TaskActor{
		Type: actorType,
		ID:   firstNonEmpty(profile.PrincipalID, profile.TargetID, profile.MemberID, fallback.ID),
		Name: firstNonEmpty(profile.DisplayName, fallback.Name),
	}
}

func ToTaskArtifact(artifact taskboard.TaskArtifact) dto.TaskArtifact {
	createdByProfile := ToTaskPrincipalProfile(artifact.CreatedByProfile)
	return dto.TaskArtifact{ID: artifact.ID, Kind: artifact.Kind, Title: artifact.Title, Payload: artifact.Payload, CreatedByProfile: createdByProfile, CreatedByRef: taskFactCompatRefFromAuthorProfile(createdByProfile, artifact.CreatedByRef), CreatedAt: artifact.CreatedAt}
}

func ToTaskArtifacts(artifacts []taskboard.TaskArtifact) []dto.TaskArtifact {
	if len(artifacts) == 0 {
		return nil
	}
	items := make([]dto.TaskArtifact, 0, len(artifacts))
	for _, artifact := range artifacts {
		items = append(items, ToTaskArtifact(artifact))
	}
	return items
}

func taskFactCompatRefFromAuthorProfile(profile *dto.AuthorProfile, fallback string) string {
	if profile == nil {
		return fallback
	}
	return firstNonEmpty(profile.MemberID, profile.PrincipalID, profile.TargetID, fallback)
}

func ToTaskActivityEvent(event taskboard.TaskActivityEvent) dto.TaskActivityEvent {
	authorProfile := ToAuthorProfile(event.AuthorProfile)
	source := ToContributionSource(event.Source)
	return dto.TaskActivityEvent{ID: event.ID, Type: event.Type, AuthorProfile: authorProfile, Source: source, ActorRef: taskActivityCompatActorRefFromAuthorProfile(authorProfile, event.ActorRef), ActorDisplayName: taskActivityCompatActorDisplayNameFromAuthorProfile(authorProfile, event.ActorDisplayName), Payload: event.Payload, SourceKind: taskActivityCompatSourceKindFromContributionSource(source, event.SourceKind), SourceMessageID: taskActivityCompatSourceMessageIDFromContributionSource(source, event.SourceMessageID), SourceActivityID: taskActivityCompatSourceActivityIDFromContributionSource(source, event.SourceActivityID), CreatedAt: event.CreatedAt}
}

func taskActivityCompatActorRefFromAuthorProfile(profile *dto.AuthorProfile, fallback string) string {
	if profile == nil {
		return fallback
	}
	return firstNonEmpty(profile.MemberID, profile.PrincipalID, profile.TargetID, fallback)
}

func taskActivityCompatActorDisplayNameFromAuthorProfile(profile *dto.AuthorProfile, fallback string) string {
	if profile == nil {
		return fallback
	}
	return firstNonEmpty(profile.DisplayName, fallback)
}

func taskActivityCompatSourceKindFromContributionSource(source *dto.ContributionSource, fallback string) string {
	if source == nil {
		return fallback
	}
	switch source.RefType {
	case "message":
		return taskActivityCompatSourceKindMessage
	case "task_activity":
		return taskActivityCompatSourceKindActivity
	default:
		return fallback
	}
}

func taskActivityCompatSourceMessageIDFromContributionSource(source *dto.ContributionSource, fallback string) string {
	if source == nil {
		return fallback
	}
	switch source.RefType {
	case "message":
		return firstNonEmpty(source.RefID, fallback)
	case "task_activity":
		return ""
	default:
		return fallback
	}
}

func taskActivityCompatSourceActivityIDFromContributionSource(source *dto.ContributionSource, fallback string) string {
	if source == nil {
		return fallback
	}
	switch source.RefType {
	case "task_activity":
		return firstNonEmpty(source.RefID, source.EventID, fallback)
	case "message":
		return ""
	default:
		return fallback
	}
}

func ToTaskTimelineEntry(entry taskboard.TaskTimelineEntry) dto.TaskTimelineEntry {
	converted := dto.TaskTimelineEntry{
		ID:        entry.ID,
		Kind:      entry.Kind,
		CreatedAt: entry.CreatedAt,
	}
	if entry.Activity != nil {
		activity := ToTaskActivityEvent(*entry.Activity)
		converted.Activity = &activity
	}
	if entry.Discussion != nil {
		discussion := ToTaskDiscussionEntry(*entry.Discussion)
		converted.Discussion = &discussion
	}
	return converted
}

func ToTaskTimelineEntries(entries []taskboard.TaskTimelineEntry) []dto.TaskTimelineEntry {
	if len(entries) == 0 {
		return nil
	}
	items := make([]dto.TaskTimelineEntry, 0, len(entries))
	for _, entry := range entries {
		items = append(items, ToTaskTimelineEntry(entry))
	}
	return items
}

func ToTaskDetail(detail taskboard.TaskDetail) dto.TaskDetail {
	var parentTask *dto.Task
	if detail.ParentTask != nil {
		convertedParentTask := ToTask(*detail.ParentTask)
		parentTask = &convertedParentTask
	}
	return dto.TaskDetail{
		Task:                     ToTask(detail.Task),
		ParentTask:               parentTask,
		Checkpoints:              ToTaskChecklistItems(detail.Checkpoints),
		Timeline:                 ToTaskTimelineEntries(detail.Timeline),
		TimelineHasMore:          detail.TimelineHasMore,
		TimelineNextCursor:       ToTaskTimelineCursorToken(detail.TimelineNextCursor),
		AcceptanceCriteria:       ToTaskChecklistItems(detail.AcceptanceCriteria),
		ReviewChecklist:          ToTaskChecklistItems(detail.ReviewChecklist),
		Subtasks:                 ToTasks(detail.Subtasks),
		SubtasksHasMore:          detail.SubtasksHasMore,
		SubtasksNextCursor:       ToTaskSubtasksCursorToken(detail.SubtasksNextCursor),
		PinnedMessageRefs:        detail.PinnedMessageRefs,
		PinnedMessageRefsHasMore: detail.PinnedMessageRefsHasMore,
		RelevantURLs:             detail.RelevantURLs,
		RelevantURLsHasMore:      detail.RelevantURLsHasMore,
		WorkspaceRefs:            detail.WorkspaceRefs,
		WorkspaceRefsHasMore:     detail.WorkspaceRefsHasMore,
		Artifacts:                ToTaskArtifacts(detail.Artifacts),
	}
}

func ToTaskTimelineListResponse(response taskboard.GetTaskTimelineResponse) dto.TaskTimelineListResponse {
	return dto.TaskTimelineListResponse{
		Entries:    ToTaskTimelineEntries(response.Entries),
		HasMore:    response.HasMore,
		NextCursor: ToTaskTimelineCursorToken(response.NextCursor),
	}
}

func ToTaskDetailResponse(response taskboard.GetTaskDetailResponse) dto.TaskDetailResponse {
	return dto.TaskDetailResponse{Detail: ToTaskDetail(response.Detail)}
}

func ToTaskSubtasksPageResponse(response taskboard.ListTaskSubtasksResponse) dto.TaskSubtasksPageResponse {
	return dto.TaskSubtasksPageResponse{
		Tasks:      ToTasks(response.Tasks),
		HasMore:    response.HasMore,
		NextCursor: ToTaskSubtasksCursorToken(response.NextCursor),
	}
}

func ToTaskViews(views []taskboard.TaskView) []dto.TaskView {
	if len(views) == 0 {
		return []dto.TaskView{}
	}
	items := make([]dto.TaskView, 0, len(views))
	for _, view := range views {
		items = append(items, dto.TaskView{TaskScopeID: view.TaskScopeID, TaskNumber: view.TaskNumber, Title: view.Title, Status: view.Status, AssigneeProfile: ToTaskPrincipalProfile(view.AssigneeProfile), CreatedByProfile: ToTaskPrincipalProfile(view.CreatedByProfile), CreatedByName: view.CreatedByName})
	}
	return items
}

// ToCreatedTaskSummaries preserves the create result order and reused marker
// while enriching each compact result with the canonical Task projection
// returned by the same create operation.
func ToCreatedTaskSummaries(created []taskboard.CreatedTask, tasks []taskboard.Task) []dto.CreatedTask {
	if len(created) == 0 {
		return []dto.CreatedTask{}
	}
	byScopeID := make(map[string]taskboard.Task, len(tasks))
	byNumber := make(map[int64]taskboard.Task, len(tasks))
	for _, task := range tasks {
		if task.TaskScopeID != "" {
			byScopeID[task.TaskScopeID] = task
		}
		byNumber[task.TaskNumber] = task
	}

	items := make([]dto.CreatedTask, 0, len(created))
	for _, task := range created {
		detail, ok := byScopeID[task.TaskScopeID]
		if !ok {
			detail, ok = byNumber[task.TaskNumber]
		}
		item := dto.CreatedTask{
			TaskScopeID: task.TaskScopeID,
			TaskNumber:  task.TaskNumber,
			Title:       task.Title,
			Reused:      task.Reused,
		}
		if ok {
			item.Status = detail.Status
			item.AssigneeProfile = ToTaskPrincipalProfile(detail.AssigneeProfile)
		}
		items = append(items, item)
	}
	return items
}

func ToClaimResults(results []taskboard.ClaimResult) []dto.ClaimResult {
	if len(results) == 0 {
		return []dto.ClaimResult{}
	}
	items := make([]dto.ClaimResult, 0, len(results))
	for _, result := range results {
		items = append(items, dto.ClaimResult{TaskNumber: result.TaskNumber, Success: result.Success, Reason: result.Reason})
	}
	return items
}

func ToTaskResponse(task taskboard.Task) dto.TaskResponse {
	return dto.TaskResponseFromTask(ToTask(task))
}

func FromPrivateAgentContextStateWindowInput(input *dto.PrivateAgentContextStateWindowInput) *taskboard.PrivateAgentContextStateWindowInput {
	if input == nil {
		return nil
	}
	return &taskboard.PrivateAgentContextStateWindowInput{
		AfterSeq:  input.AfterSeq,
		BeforeSeq: input.BeforeSeq,
		Limit:     input.Limit,
	}
}

func ToTaskCollaboratorSource(source taskboard.TaskCollaboratorSource) dto.TaskCollaboratorSource {
	return dto.TaskCollaboratorSource{ScopeType: source.ScopeType, ScopeID: source.ScopeID, RelationID: source.RelationID, Role: source.Role}
}

func ToTaskCollaboratorSources(sources []taskboard.TaskCollaboratorSource) []dto.TaskCollaboratorSource {
	if len(sources) == 0 {
		return nil
	}
	items := make([]dto.TaskCollaboratorSource, 0, len(sources))
	for _, source := range sources {
		items = append(items, ToTaskCollaboratorSource(source))
	}
	return items
}

func ToTaskCollaborator(collaborator taskboard.TaskCollaborator) dto.TaskCollaborator {
	memberProfile := ToTaskPrincipalProfile(collaborator.MemberProfile)
	addedByProfile := ToTaskPrincipalProfile(collaborator.AddedByProfile)
	removedByProfile := ToTaskPrincipalProfile(collaborator.RemovedByProfile)
	var removedBy *dto.TaskActor
	if collaborator.RemovedBy != nil || removedByProfile != nil {
		fallback := taskboard.TaskActor{}
		if collaborator.RemovedBy != nil {
			fallback = *collaborator.RemovedBy
		}
		actor := taskCompatActorFromAuthorProfile(removedByProfile, fallback)
		removedBy = &actor
	}
	return dto.TaskCollaborator{
		ID:                     collaborator.ID,
		ScopeType:              collaborator.ScopeType,
		ScopeID:                collaborator.ScopeID,
		ExecutionOwnerMemberID: collaborator.ExecutionOwnerMemberID,
		MemberID:               collaborator.MemberID,
		MemberProfile:          memberProfile,
		Member:                 taskCompatActorFromAuthorProfile(memberProfile, collaborator.Member),
		Role:                   collaborator.Role,
		AddedByMemberID:        collaborator.AddedByMemberID,
		AddedByProfile:         addedByProfile,
		AddedBy:                taskCompatActorFromAuthorProfile(addedByProfile, collaborator.AddedBy),
		AddedAt:                collaborator.AddedAt,
		NotificationState:      collaborator.NotificationState,
		NotificationQueuedAt:   collaborator.NotificationQueuedAt,
		NotifiedAt:             collaborator.NotifiedAt,
		ExpiresAt:              collaborator.ExpiresAt,
		LeftAt:                 collaborator.LeftAt,
		RemovedAt:              collaborator.RemovedAt,
		RemovedByMemberID:      collaborator.RemovedByMemberID,
		RemovedByProfile:       removedByProfile,
		RemovedBy:              removedBy,
		Active:                 collaborator.Active,
	}
}

func ToTaskCollaborators(collaborators []taskboard.TaskCollaborator) []dto.TaskCollaborator {
	if len(collaborators) == 0 {
		return nil
	}
	items := make([]dto.TaskCollaborator, 0, len(collaborators))
	for _, collaborator := range collaborators {
		items = append(items, ToTaskCollaborator(collaborator))
	}
	return items
}

func ToListTaskCollaboratorsResponse(response taskboard.ListTaskCollaboratorsResponse) dto.ListTaskCollaboratorsResponse {
	ownerProfile := ToTaskPrincipalProfile(response.OwnerProfile)
	return dto.ListTaskCollaboratorsResponse{
		ExecutionOwnerMemberID: response.ExecutionOwnerMemberID,
		OwnerProfile:           ownerProfile,
		Owner:                  taskCompatActorFromAuthorProfile(ownerProfile, response.Owner),
		Collaborators:          ToTaskCollaborators(response.Collaborators),
	}
}

func ToAgentCollaboratorSource(source taskboard.AgentCollaboratorSource) dto.AgentCollaboratorSource {
	return dto.AgentCollaboratorSource{
		ScopeType:          source.ScopeType,
		ScopeID:            source.ScopeID,
		ScopeDisplayName:   source.ScopeDisplayName,
		ChannelID:          source.ChannelID,
		ChannelDisplayName: source.ChannelDisplayName,
		RelationID:         source.RelationID,
		Role:               source.Role,
		ExpiresAt:          source.ExpiresAt,
		Active:             source.Active,
	}
}

func ToAgentCollaboratorSources(sources []taskboard.AgentCollaboratorSource) []dto.AgentCollaboratorSource {
	if len(sources) == 0 {
		return []dto.AgentCollaboratorSource{}
	}
	items := make([]dto.AgentCollaboratorSource, 0, len(sources))
	for _, source := range sources {
		items = append(items, ToAgentCollaboratorSource(source))
	}
	return items
}

func ToAgentCollaboratorSummary(summary taskboard.AgentCollaboratorSummary) dto.AgentCollaboratorSummary {
	memberProfile := ToTaskPrincipalProfile(summary.MemberProfile)
	member := taskCompatActorFromAuthorProfile(memberProfile, summary.Member)
	return dto.AgentCollaboratorSummary{
		MemberID:          summary.MemberID,
		MemberProfile:     memberProfile,
		Member:            member,
		ScopeCount:        summary.ScopeCount,
		SoonExpiringCount: summary.SoonExpiringCount,
		Sources:           ToAgentCollaboratorSources(summary.Sources),
	}
}

func ToAgentCollaboratorSummaries(summaries []taskboard.AgentCollaboratorSummary) []dto.AgentCollaboratorSummary {
	if len(summaries) == 0 {
		return []dto.AgentCollaboratorSummary{}
	}
	items := make([]dto.AgentCollaboratorSummary, 0, len(summaries))
	for _, summary := range summaries {
		items = append(items, ToAgentCollaboratorSummary(summary))
	}
	return items
}

func ToListAgentCollaboratorsResponse(response taskboard.ListAgentCollaboratorsResponse) dto.ListAgentCollaboratorsResponse {
	collaborators := ToAgentCollaboratorSummaries(response.Collaborators)
	if len(collaborators) == 0 && len(response.People) > 0 {
		collaborators = ToAgentCollaboratorSummaries(response.People)
	}
	ownerProfile := ToTaskPrincipalProfile(response.OwnerProfile)
	owner := taskCompatActorFromAuthorProfile(ownerProfile, response.Owner)
	return dto.ListAgentCollaboratorsResponse{
		ExecutionOwnerMemberID:   response.ExecutionOwnerMemberID,
		OwnerProfile:             ownerProfile,
		Owner:                    owner,
		SourceScopeDisplayName:   response.SourceScopeDisplayName,
		SourceChannelDisplayName: response.SourceChannelDisplayName,
		People:                   collaborators,
		Collaborators:            collaborators,
	}
}

func ToTaskCollaboratorResponse(collaborator taskboard.TaskCollaborator) dto.TaskCollaboratorResponse {
	return dto.TaskCollaboratorResponse{Collaborator: ToTaskCollaborator(collaborator)}
}

func ToAddTaskCollaboratorResponse(response taskboard.AddTaskCollaboratorResponse) dto.TaskCollaboratorResponse {
	return ToTaskCollaboratorResponse(response.Collaborator)
}

func ToRemoveTaskCollaboratorResponse(response taskboard.RemoveTaskCollaboratorResponse) dto.TaskCollaboratorResponse {
	return ToTaskCollaboratorResponse(response.Collaborator)
}

func ToLeaveTaskCollaboratorResponse(response taskboard.LeaveTaskCollaboratorResponse) dto.TaskCollaboratorResponse {
	return ToTaskCollaboratorResponse(response.Collaborator)
}

func ToEffectiveTaskCollaborator(collaborator taskboard.EffectiveTaskCollaborator) dto.EffectiveTaskCollaborator {
	memberProfile := ToTaskPrincipalProfile(collaborator.MemberProfile)
	return dto.EffectiveTaskCollaborator{
		MemberID:      collaborator.MemberID,
		MemberProfile: memberProfile,
		Member:        taskCompatActorFromAuthorProfile(memberProfile, collaborator.Member),
		Role:          collaborator.Role,
		Sources:       ToTaskCollaboratorSources(collaborator.Sources),
	}
}

func ToEffectiveTaskCollaborators(collaborators []taskboard.EffectiveTaskCollaborator) []dto.EffectiveTaskCollaborator {
	if len(collaborators) == 0 {
		return nil
	}
	items := make([]dto.EffectiveTaskCollaborator, 0, len(collaborators))
	for _, collaborator := range collaborators {
		items = append(items, ToEffectiveTaskCollaborator(collaborator))
	}
	return items
}

func ToListEffectiveTaskCollaboratorsResponse(response taskboard.ListEffectiveTaskCollaboratorsResponse) dto.ListEffectiveTaskCollaboratorsResponse {
	ownerProfile := ToTaskPrincipalProfile(response.OwnerProfile)
	return dto.ListEffectiveTaskCollaboratorsResponse{
		ExecutionOwnerMemberID: response.ExecutionOwnerMemberID,
		OwnerProfile:           ownerProfile,
		Owner:                  taskCompatActorFromAuthorProfile(ownerProfile, response.Owner),
		People:                 ToEffectiveTaskCollaborators(response.People),
		Collaborators:          ToEffectiveTaskCollaborators(response.Collaborators),
	}
}

func ToPrivateAgentContextRelationSource(source *taskboard.PrivateAgentContextRelationSource) *dto.PrivateAgentContextRelationSource {
	if source == nil {
		return nil
	}
	return &dto.PrivateAgentContextRelationSource{
		Kind:       source.Kind,
		ScopeType:  source.ScopeType,
		ScopeID:    source.ScopeID,
		RelationID: source.RelationID,
		Label:      source.Label,
	}
}

func ToPrivateAgentContextState(state taskboard.PrivateAgentContextState) dto.PrivateAgentContextState {
	return dto.PrivateAgentContextState{
		MessageID:       state.MessageID,
		State:           dto.PrivateAgentContextEligibilityState(state.State),
		TargetScopeType: state.TargetScopeType,
		TargetScopeID:   state.TargetScopeID,
		Relation:        ToPrivateAgentContextRelationSource(state.Relation),
		CanShare:        state.CanShare,
		CanWake:         state.CanWake,
	}
}

func ToPrivateAgentContextStates(states []taskboard.PrivateAgentContextState) []dto.PrivateAgentContextState {
	if len(states) == 0 {
		return nil
	}
	items := make([]dto.PrivateAgentContextState, 0, len(states))
	for _, state := range states {
		items = append(items, ToPrivateAgentContextState(state))
	}
	return items
}

func ToResolvePrivateAgentContextStateResponse(response taskboard.ResolvePrivateAgentContextStateResponse) dto.ResolvePrivateAgentContextStateResponse {
	return dto.ResolvePrivateAgentContextStateResponse{States: ToPrivateAgentContextStates(response.States)}
}

func ToResolvePrivateAgentContextContributionCapabilityResponse(response taskboard.ResolvePrivateAgentContextContributionCapabilityResponse) dto.ResolvePrivateAgentContextContributionCapabilityResponse {
	return dto.ResolvePrivateAgentContextContributionCapabilityResponse{
		TargetScopeType:        response.TargetScopeType,
		TargetScopeID:          response.TargetScopeID,
		ExecutionOwnerMemberID: response.ExecutionOwnerMemberID,
		Relation:               response.Relation,
		RelationSource:         ToPrivateAgentContextRelationSource(response.RelationSource),
		CanContributeContext:   response.CanContributeContext,
		CanWake:                response.CanWake,
		CanShare:               response.CanShare,
	}
}

func ToPrivateAgentMentionCandidate(candidate taskboard.PrivateAgentMentionCandidate) dto.PrivateAgentMentionCandidate {
	ownerProfile := ToTaskPrincipalProfile(candidate.OwnerProfile)
	ownerMemberID, ownerUserID, ownerName, ownerDisplayName := privateAgentMentionOwnerCompatFromAuthorProfile(ownerProfile, candidate)
	return dto.PrivateAgentMentionCandidate{
		ID:               candidate.ID,
		Name:             candidate.Name,
		DisplayName:      candidate.DisplayName,
		MemberID:         candidate.MemberID,
		AvatarURL:        candidate.AvatarURL,
		OwnerProfile:     ownerProfile,
		OwnerMemberID:    ownerMemberID,
		OwnerUserID:      ownerUserID,
		OwnerName:        ownerName,
		OwnerDisplayName: ownerDisplayName,
		Relation:         candidate.Relation,
		RelationSource:   ToPrivateAgentContextRelationSource(candidate.RelationSource),
	}
}

func privateAgentMentionOwnerCompatFromAuthorProfile(profile *dto.AuthorProfile, fallback taskboard.PrivateAgentMentionCandidate) (string, string, string, string) {
	if profile == nil {
		return fallback.OwnerMemberID, fallback.OwnerUserID, fallback.OwnerName, fallback.OwnerDisplayName
	}
	return firstNonEmpty(profile.MemberID, fallback.OwnerMemberID),
		firstNonEmpty(profile.PrincipalID, fallback.OwnerUserID),
		firstNonEmpty(profile.DisplayName, fallback.OwnerName),
		firstNonEmpty(profile.DisplayName, fallback.OwnerDisplayName)
}

func ToPrivateAgentMentionCandidates(candidates []taskboard.PrivateAgentMentionCandidate) []dto.PrivateAgentMentionCandidate {
	if len(candidates) == 0 {
		return nil
	}
	items := make([]dto.PrivateAgentMentionCandidate, 0, len(candidates))
	for _, candidate := range candidates {
		items = append(items, ToPrivateAgentMentionCandidate(candidate))
	}
	return items
}

func ToListPrivateAgentMentionCandidatesResponse(response taskboard.ListPrivateAgentMentionCandidatesResponse) dto.ListPrivateAgentMentionCandidatesResponse {
	return dto.ListPrivateAgentMentionCandidatesResponse{Candidates: ToPrivateAgentMentionCandidates(response.Candidates)}
}
