import type {
  BackendTask,
  ProcessArtifact,
  ProcessEvent,
  ProcessRun,
  TaskTimelineCard,
  TaskTimelineCardType,
} from '@/types';
import { getCurrentAppLocale, pickAppText, type AppLocale } from '@/lib/i18n/core';

export type BuildTaskTimelineCardsInput = {
  task: BackendTask;
  processRuns?: ProcessRun[];
  processEvents?: ProcessEvent[];
  processArtifacts?: ProcessArtifact[];
  locale?: AppLocale | string;
};

const TIMELINE_CARD_TYPES = new Set<TaskTimelineCardType>([
  'task_created',
  'plan',
  'skill',
  'tool_call',
  'tool_result',
  'next_step',
  'agent_team',
  'agent_progress',
  'agent_handoff',
  'artifact',
  'error',
  'result',
  'result_history',
  'acceptance',
]);

const RESULT_STATUSES = new Set(['awaiting_acceptance', 'closed', 'abandoned', 'cancelled', 'error']);

function isTimelineCardType(value: unknown): value is TaskTimelineCardType {
  return typeof value === 'string' && TIMELINE_CARD_TYPES.has(value as TaskTimelineCardType);
}

function toTime(value: string): number | null {
  const parsed = new Date(value).getTime();
  return Number.isFinite(parsed) ? parsed : null;
}

function firstString(...values: unknown[]): string | undefined {
  for (const value of values) {
    if (typeof value !== 'string') continue;
    const trimmed = value.trim();
    if (trimmed) return trimmed;
  }
  return undefined;
}

function stringList(value: unknown): string[] {
  if (Array.isArray(value)) {
    return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
  }
  if (typeof value === 'string' && value.trim()) {
    return [value.trim()];
  }
  return [];
}

function normalizeSkillNames(metadata: Record<string, unknown> | undefined): string[] | undefined {
  if (!metadata || (!('skill_names' in metadata) && !('selected_skill_names' in metadata))) {
    return undefined;
  }
  const names = [
    ...stringList(metadata.skill_names),
    ...stringList(metadata.selected_skill_names),
  ];
  return Array.from(new Set(names));
}

function cardTypeForEvent(event: ProcessEvent): TaskTimelineCardType | null {
  const timelineType = event.metadata?.timeline_type;
  if (isTimelineCardType(timelineType)) {
    return timelineType;
  }

  switch (String(event.kind)) {
    case 'task_planned':
    case 'run_started':
      return 'plan';
    case 'skill_selected':
      return 'skill';
    case 'tool_call_started':
      return 'tool_call';
    case 'tool_call_finished':
      return 'tool_result';
    case 'agent_team_created':
      return 'agent_team';
    case 'agent_handoff':
      return 'agent_handoff';
    case 'agent_finished':
    case 'run_progress':
    case 'run_finished':
      return 'agent_progress';
    case 'task_result_ready':
      return 'result';
    case 'task_acceptance_recorded':
      return 'acceptance';
    case 'task_error':
      return 'error';
    default:
      if (event.status === 'error') {
        return 'error';
      }
      return null;
  }
}

function titleForCard(type: TaskTimelineCardType, actorName?: string, locale: AppLocale | string = getCurrentAppLocale()): string {
  switch (type) {
    case 'task_created':
      return pickAppText(locale, '任务已创建', 'Task created');
    case 'plan':
      return pickAppText(locale, '执行计划', 'Execution plan');
    case 'skill':
      return pickAppText(locale, '选择 Skill', 'Skill selected');
    case 'tool_call':
      return actorName
        ? pickAppText(locale, `调用工具：${actorName}`, `Calling tool: ${actorName}`)
        : pickAppText(locale, '调用工具', 'Tool call');
    case 'tool_result':
      return actorName
        ? pickAppText(locale, `工具结果：${actorName}`, `Tool result: ${actorName}`)
        : pickAppText(locale, '工具结果', 'Tool result');
    case 'next_step':
      return pickAppText(locale, '下一步', 'Next step');
    case 'agent_team':
      return pickAppText(locale, '启动 Agent Team', 'Agent team started');
    case 'agent_progress':
      return actorName || pickAppText(locale, 'Agent 进展', 'Agent progress');
    case 'agent_handoff':
      return pickAppText(locale, 'Agent 交接', 'Agent handoff');
    case 'artifact':
      return pickAppText(locale, '生成产物', 'Artifact generated');
    case 'error':
      return pickAppText(locale, '执行遇到问题', 'Execution issue');
    case 'result':
      return pickAppText(locale, '本轮结果', 'Run result');
    case 'result_history':
      return pickAppText(locale, '历史结果版本', 'Previous result versions');
    case 'acceptance':
      return pickAppText(locale, '任务验收', 'Task acceptance');
  }
}

function summaryForEvent(event: ProcessEvent): string | undefined {
  return firstString(
    event.metadata?.result_summary,
    event.metadata?.reason,
    event.metadata?.action_summary,
    event.text,
  );
}

function detailsForEvent(event: ProcessEvent): Record<string, unknown> | undefined {
  const skillNames = normalizeSkillNames(event.metadata);
  if (!event.metadata && !skillNames) {
    return undefined;
  }
  return {
    ...(event.metadata ?? {}),
    ...(skillNames ? { skill_names: skillNames } : {}),
  };
}

function feedbackCreatedAt(feedback: Record<string, unknown>, task: BackendTask): string {
  return firstString(feedback.created_at, task.updated_at, task.created_at) ?? task.created_at;
}

function feedbackSummary(feedback: Record<string, unknown>): string | undefined {
  return firstString(feedback.comment, feedback.summary, feedback.acceptance_type);
}

function acceptanceTypeFromRecord(record: Record<string, unknown> | undefined): string | null {
  return firstString(record?.acceptance_type, record?.feedback_type)?.toLowerCase() ?? null;
}

function resultSummary(task: BackendTask): string | undefined {
  return firstString(
    task.metadata?.result_summary,
    task.metadata?.summary,
    task.close_reason,
    task.validation_result?.summary,
  );
}

function assistantResultForRun(task: BackendTask, runId: string | null | undefined): string | undefined {
  if (!runId) return undefined;
  const run = (task.runs ?? []).find((item) => item.run_id === runId);
  if (!run) return undefined;
  const assistantMessages = run.messages.filter((message) => message.role === 'assistant' && message.content.trim());
  return lastItem(assistantMessages)?.content.trim();
}

function resultSummaryForEvent(task: BackendTask, event: ProcessEvent): string | undefined {
  return firstString(assistantResultForRun(task, event.run_id), summaryForEvent(event));
}

function fallbackResultSummary(task: BackendTask): string | undefined {
  return firstString(assistantResultForRun(task, lastItem(task.run_ids)), resultSummary(task));
}

function buildRunMap(processRuns: ProcessRun[]): Map<string, ProcessRun> {
  const map = new Map<string, ProcessRun>();
  for (const run of processRuns) {
    map.set(run.run_id, run);
  }
  return map;
}

function lastItem<T>(items: T[]): T | null {
  return items.length > 0 ? items[items.length - 1] : null;
}

function compareCardsByCreatedAt(
  a: { card: TaskTimelineCard; index: number },
  b: { card: TaskTimelineCard; index: number },
): number {
  const aTime = toTime(a.card.createdAt);
  const bTime = toTime(b.card.createdAt);

  if (aTime === null && bTime === null) {
    return a.index - b.index;
  }
  if (aTime === null) {
    return 1;
  }
  if (bTime === null) {
    return -1;
  }

  return aTime - bTime || a.index - b.index;
}

type AcceptanceEventIdentity = {
  runId: string | null;
  acceptanceType: string | null;
};

function isCoveredByAcceptanceEvent(
  feedback: Record<string, unknown>,
  acceptanceEvents: AcceptanceEventIdentity[],
): boolean {
  const feedbackType = acceptanceTypeFromRecord(feedback);
  if (!feedbackType) return false;

  const feedbackRunId = firstString(feedback.run_id) ?? null;
  const matchingTypeEvents = acceptanceEvents.filter((event) => event.acceptanceType === feedbackType);

  if (feedbackRunId) {
    return (
      matchingTypeEvents.some((event) => event.runId === feedbackRunId) ||
      (matchingTypeEvents.length === 1 && !matchingTypeEvents[0].runId)
    );
  }

  return matchingTypeEvents.length === 1;
}

function cardTime(card: TaskTimelineCard): number {
  return toTime(card.createdAt) ?? Number.MAX_SAFE_INTEGER;
}

function cardComment(card: TaskTimelineCard): string | undefined {
  return firstString(card.details?.comment, card.summary);
}

function toolCallKeyFromEvent(event: ProcessEvent): string | null {
  const toolCallId = firstString(event.metadata?.tool_call_id);
  if (toolCallId) return `${event.run_id}:${toolCallId}`;

  const toolName = firstString(event.metadata?.tool_name, event.actor_name, event.actor_id);
  if (toolName) return `${event.run_id}:${toolName}`;

  return null;
}

function buildToolResultStatusByCall(processEvents: ProcessEvent[]): Map<string, string> {
  const statuses = new Map<string, string>();
  for (const event of processEvents) {
    if (cardTypeForEvent(event) !== 'tool_result') continue;
    const key = toolCallKeyFromEvent(event);
    if (!key) continue;
    statuses.set(key, event.status || 'done');
  }
  return statuses;
}

function buildResultHistoryCard(
  task: BackendTask,
  resultCards: TaskTimelineCard[],
  acceptanceCards: TaskTimelineCard[],
  locale: AppLocale | string,
): TaskTimelineCard {
  const versions = resultCards.map((resultCard) => {
    const acceptanceCard = acceptanceCards
      .filter((card) => card.runId === resultCard.runId)
      .sort((a, b) => cardTime(a) - cardTime(b))
      .at(-1);
    return {
      runId: resultCard.runId ?? null,
      result: resultCard.summary ?? '',
      createdAt: resultCard.createdAt,
      status: acceptanceCard?.status ?? resultCard.status ?? null,
      acceptanceType: acceptanceCard?.status ?? null,
      comment: acceptanceCard ? cardComment(acceptanceCard) ?? '' : '',
      acceptedAt: acceptanceCard?.createdAt ?? null,
    };
  });

  return {
    id: `${task.task_id}:result-history`,
    taskId: task.task_id,
    type: 'result_history',
    title: titleForCard('result_history', undefined, locale),
    summary: pickAppText(
      locale,
      `${resultCards.length} 历史结果版本`,
      `${resultCards.length} previous result ${resultCards.length === 1 ? 'version' : 'versions'}`,
    ),
    createdAt: resultCards[0]?.createdAt ?? task.created_at,
    details: { versions },
  };
}

function collapseHistoricalResults(task: BackendTask, cards: TaskTimelineCard[], locale: AppLocale | string): TaskTimelineCard[] {
  const resultCards = cards.filter((card) => card.type === 'result');
  if (resultCards.length <= 1) return cards;

  const finalAcceptedRunId = firstString(task.metadata?.final_accepted_run_id);
  const latestResult =
    (finalAcceptedRunId ? resultCards.find((card) => card.runId === finalAcceptedRunId) : undefined) ??
    [...resultCards].sort((a, b) => cardTime(a) - cardTime(b)).at(-1);
  if (!latestResult) return cards;

  const oldResults = resultCards
    .filter((card) => card.id !== latestResult.id)
    .sort((a, b) => cardTime(a) - cardTime(b));
  if (oldResults.length === 0) return cards;

  const oldRunIds = new Set(oldResults.map((card) => card.runId).filter(Boolean));
  const oldAcceptances = cards
    .filter((card) => card.type === 'acceptance' && oldRunIds.has(card.runId))
    .sort((a, b) => cardTime(a) - cardTime(b));
  const foldedIds = new Set([...oldResults, ...oldAcceptances].map((card) => card.id));
  const historyCard = buildResultHistoryCard(task, oldResults, oldAcceptances, locale);
  const firstOldResultIndex = cards.findIndex((card) => card.id === oldResults[0].id);
  const output: TaskTimelineCard[] = [];

  for (let index = 0; index < cards.length; index += 1) {
    if (index === firstOldResultIndex) {
      output.push(historyCard);
    }
    if (!foldedIds.has(cards[index].id)) {
      output.push(cards[index]);
    }
  }

  return output;
}

export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): TaskTimelineCard[] {
  const { task } = input;
  const locale = input.locale ?? getCurrentAppLocale();
  const processRuns = input.processRuns ?? task.process_runs ?? [];
  const processEvents = input.processEvents ?? task.process_events ?? [];
  const processArtifacts = input.processArtifacts ?? task.process_artifacts ?? [];
  const runsById = buildRunMap(processRuns);
  const toolResultStatusByCall = buildToolResultStatusByCall(processEvents);
  const runsWithProgressEvents = new Set<string>();
  const acceptanceEvents: AcceptanceEventIdentity[] = [];
  let hasResultEventCard = false;
  const cards: TaskTimelineCard[] = [
    {
      id: `${task.task_id}:created`,
      taskId: task.task_id,
      type: 'task_created',
      title: titleForCard('task_created', undefined, locale),
      summary: firstString(task.short_title, task.description, task.goal),
      actorName: task.creator,
      status: task.status,
      createdAt: task.created_at,
      details: task.metadata,
    },
  ];

  for (const event of processEvents) {
    const type = cardTypeForEvent(event);
    if (!type) continue;
    if (type === 'agent_progress') {
      runsWithProgressEvents.add(event.run_id);
    }
    if (type === 'result') {
      hasResultEventCard = true;
    }
    if (type === 'acceptance') {
      acceptanceEvents.push({
        runId: firstString(event.run_id) ?? null,
        acceptanceType: acceptanceTypeFromRecord(event.metadata),
      });
    }

    cards.push({
      id: event.event_id,
      taskId: task.task_id,
      runId: event.run_id,
      parentRunId: event.parent_run_id,
      type,
      title: titleForCard(type, event.actor_name, locale),
      summary: type === 'result' ? resultSummaryForEvent(task, event) : summaryForEvent(event),
      actorName: event.actor_name,
      status:
        type === 'tool_call'
          ? toolResultStatusByCall.get(toolCallKeyFromEvent(event) ?? '') ?? event.status
          : event.status,
      createdAt: event.created_at,
      details: detailsForEvent(event),
    });
  }

  for (const run of processRuns) {
    if (!run.parent_run_id) continue;
    if (runsWithProgressEvents.has(run.run_id)) continue;

    cards.push({
      id: `${run.run_id}:fallback-progress`,
      taskId: task.task_id,
      runId: run.run_id,
      parentRunId: run.parent_run_id,
      type: 'agent_progress',
      title: titleForCard('agent_progress', run.actor_name, locale),
      summary: firstString(run.summary, run.title),
      actorName: run.actor_name,
      status: run.status,
      createdAt: run.started_at,
      details: run.metadata,
    });
  }

  for (const artifact of processArtifacts) {
    const run = runsById.get(artifact.run_id);
    cards.push({
      id: artifact.artifact_id,
      taskId: task.task_id,
      runId: artifact.run_id,
      parentRunId: run?.parent_run_id,
      type: 'artifact',
      title: titleForCard('artifact', undefined, locale),
      summary: firstString(artifact.title),
      actorName: artifact.actor_name,
      createdAt: artifact.created_at,
      relatedArtifactIds: [artifact.artifact_id],
      details: {
        ...(artifact.metadata ?? {}),
        artifact_type: artifact.artifact_type,
        title: artifact.title,
      },
    });
  }

  if (RESULT_STATUSES.has(task.status) && !hasResultEventCard) {
    cards.push({
      id: `${task.task_id}:result`,
      taskId: task.task_id,
      runId: lastItem(task.run_ids),
      type: 'result',
      title: titleForCard('result', undefined, locale),
      summary: fallbackResultSummary(task),
      status: task.status,
      createdAt: task.closed_at ?? task.updated_at ?? task.created_at,
      details: task.validation_result ?? undefined,
    });
  }

  for (let index = 0; index < task.feedback.length; index += 1) {
    const feedback = task.feedback[index];
    const runId = firstString(feedback.run_id) ?? null;
    const createdAt = feedbackCreatedAt(feedback, task);
    if (isCoveredByAcceptanceEvent(feedback, acceptanceEvents)) continue;

    cards.push({
      id: `${task.task_id}:acceptance:${index}`,
      taskId: task.task_id,
      runId,
      type: 'acceptance',
      title: titleForCard('acceptance', undefined, locale),
      summary: feedbackSummary(feedback),
      status: firstString(feedback.acceptance_type),
      createdAt,
      details: feedback,
    });
  }

  const sortedCards = cards
    .map((card, index) => ({ card, index }))
    .sort(compareCardsByCreatedAt)
    .map(({ card }) => card);

  return collapseHistoricalResults(task, sortedCards, locale);
}
