import type {
  ChatDocumentAttachment,
  ChatImageAttachment,
  ChatTimelineItem,
  ChatTimelineKnowledgeHit,
  ChatTimelineMessageItem,
  ChatTimelineSkillInvocationStatus,
} from '@easyflow/common-ui';

import type { AgentChatMessageRecord } from '../api';

import { ChatTimelineBuilder } from '@easyflow/common-ui';

import { projectArtifactPayload } from '../../shared/agent-agui/artifact-projection';
import { easyFlowAguiCustomEvent } from '../../shared/agent-agui/custom-events';

function asText(value: unknown) {
  return value === null || value === undefined ? '' : String(value);
}

function asRecord(value: unknown): Record<string, any> {
  return value && typeof value === 'object' && !Array.isArray(value)
    ? (value as Record<string, any>)
    : {};
}

function asArray(value: unknown): any[] {
  return Array.isArray(value) ? value : [];
}

function asTimestamp(value: unknown) {
  if (!value) {
    return Date.now();
  }
  const timestamp = new Date(String(value)).getTime();
  return Number.isFinite(timestamp) ? timestamp : Date.now();
}

function optionalTimestamp(value: unknown) {
  if (!value) return undefined;
  const timestamp = new Date(String(value)).getTime();
  return Number.isFinite(timestamp) ? timestamp : undefined;
}

function applyHistoryTurnTimings(
  items: ChatTimelineItem[],
  records: AgentChatMessageRecord[],
) {
  const timings = new Map<
    string,
    { finishedAt?: number; firstAt: number; startedAt?: number }
  >();
  for (const record of records) {
    const roundId = asText(record.roundId).trim();
    const createdAt = optionalTimestamp(record.created);
    if (!roundId || createdAt === undefined) continue;
    const current = timings.get(roundId) || { firstAt: createdAt };
    current.firstAt = Math.min(current.firstAt, createdAt);
    const role = normalizeRole(record.senderRole);
    if (role === 'user') {
      current.startedAt = Math.min(current.startedAt ?? createdAt, createdAt);
    } else {
      current.finishedAt = Math.max(current.finishedAt ?? createdAt, createdAt);
    }
    timings.set(roundId, current);
  }
  for (const item of items) {
    const timing = item.roundId ? timings.get(item.roundId) : undefined;
    if (!timing) continue;
    item.turnStartedAt = timing.startedAt ?? timing.firstAt;
    if (timing.finishedAt !== undefined) {
      item.turnFinishedAt = timing.finishedAt;
    }
  }
}

function normalizeRole(value: unknown): 'assistant' | 'system' | 'user' {
  const role = asText(value).toLowerCase();
  if (role === 'assistant' || role === 'system' || role === 'user') {
    return role;
  }
  return 'assistant';
}

function normalizeToolName(value: unknown) {
  return asText(value).trim();
}

function normalizeToolCallId(payload: Record<string, any>) {
  return asText(payload.toolCallId ?? payload.tool_call_id ?? payload.id);
}

function isBlankToolName(value: unknown) {
  return !normalizeToolName(value);
}

function shouldSkipToolProjection(value: unknown) {
  const normalizedName = normalizeToolName(value).toLowerCase();
  return (
    normalizedName === 'context_reload' || normalizedName === '__fragment__'
  );
}

function normalizeToolCallName(payload: Record<string, any>) {
  const fn = asRecord(payload.function);
  return normalizeToolName(
    payload.toolDisplayName ?? payload.name ?? payload.toolName ?? fn.name,
  );
}

function normalizeToolCallInput(payload: Record<string, any>) {
  const fn = asRecord(payload.function);
  return payload.arguments ?? payload.input ?? fn.arguments;
}

function statusKeyForProjection(
  payload: Record<string, any>,
  metadata?: Partial<ChatTimelineMessageItem>,
  fallback = 'status',
) {
  const statusKey = asText(payload.statusKey) || fallback;
  const roundId = asText(metadata?.roundId);
  return roundId ? `${statusKey}:${roundId}` : statusKey;
}

const terminalSkillInvocationStatuses =
  new Set<ChatTimelineSkillInvocationStatus>([
    'CANCELLED',
    'FAILED',
    'INCOMPLETE',
    'SUCCESS',
  ]);

function statusFromRuntimeEventType(
  eventType: string,
): ChatTimelineSkillInvocationStatus | undefined {
  if (eventType === 'SKILL_RESULT') return 'SUCCESS';
  if (eventType === 'SKILL_FAILED') return 'FAILED';
  return undefined;
}

function normalizeHistorySkillInvocation(value: unknown, direct: boolean) {
  const event = asRecord(value);
  const eventName = asText(event.name ?? event.eventName);
  const eventType = asText(event.eventType ?? event.type).toUpperCase();
  const isSkillRuntimeEvent =
    eventType === 'SKILL_FAILED' || eventType === 'SKILL_RESULT';
  if (
    (!direct &&
      eventName !== easyFlowAguiCustomEvent.skillInvocationStatus &&
      !isSkillRuntimeEvent) ||
    (eventName && eventName !== easyFlowAguiCustomEvent.skillInvocationStatus)
  ) {
    return undefined;
  }
  const payload = asRecord(
    event.value ?? event.payload ?? event.payloadJson ?? event,
  );
  const rawStatus = asText(payload.status).toUpperCase();
  const status = (rawStatus || statusFromRuntimeEventType(eventType)) as
    | ChatTimelineSkillInvocationStatus
    | undefined;
  if (!status || !terminalSkillInvocationStatuses.has(status)) {
    return undefined;
  }
  const statusKey = asText(payload.statusKey).trim();
  if (!statusKey) {
    return undefined;
  }
  return {
    displayName:
      asText(payload.skillDisplayName).trim() ||
      asText(payload.skillName).trim() ||
      '技能',
    status,
    statusKey,
  };
}

function projectHistorySkillInvocations(
  items: ChatTimelineItem[],
  payload: Record<string, any>,
  metadata: Partial<ChatTimelineMessageItem>,
) {
  const directSource = [
    ...asArray(payload.skillInvocationStatuses),
    ...asArray(payload.skillInvocations),
  ];
  const source =
    directSource.length > 0 ? directSource : asArray(payload.runtimeEvents);
  for (const value of source) {
    const invocation = normalizeHistorySkillInvocation(
      value,
      directSource.length > 0,
    );
    if (invocation) {
      ChatTimelineBuilder.upsertSkillInvocationStatus(items, {
        ...metadata,
        ...invocation,
      });
    }
  }
}

function projectHistoryArtifacts(
  items: ChatTimelineItem[],
  payload: Record<string, any>,
  metadata: Partial<ChatTimelineMessageItem>,
) {
  let source: any[];
  if (Array.isArray(payload.artifacts)) {
    source = payload.artifacts;
  } else if (Array.isArray(payload.artifactPublishedEvents)) {
    source = payload.artifactPublishedEvents;
  } else {
    source = asArray(payload.runtimeEvents)
      .map((value) => asRecord(value))
      .filter(
        (event) =>
          asText(event.name ?? event.eventName) ===
          easyFlowAguiCustomEvent.artifactPublished,
      )
      .map((event) =>
        asRecord(event.value ?? event.payload ?? event.payloadJson),
      );
  }
  for (const artifact of source) {
    projectArtifactPayload(items, asRecord(artifact), metadata);
  }
}

function normalizeMetadata(record: AgentChatMessageRecord) {
  return {
    createdAt: asTimestamp(record.created),
    id: `history-${record.id || record.roundId || Date.now()}`,
    roundId: asText(record.roundId),
    roundNo: record.roundNo,
    selectedVariantIndex: record.selectedVariantIndex,
    switchable: false,
    variantCount: record.variantCount,
    variantIndex: record.variantIndex,
  } satisfies Partial<ChatTimelineMessageItem>;
}

function assistantMetadata(
  record: AgentChatMessageRecord,
  suffix?: string,
): Partial<ChatTimelineMessageItem> {
  const metadata = normalizeMetadata(record);
  return suffix ? { ...metadata, id: `${metadata.id}-${suffix}` } : metadata;
}

function normalizeKnowledgeItems(payload: Record<string, any>) {
  let rawItems = asArray(payload.items);
  if (rawItems.length === 0) {
    rawItems = asArray(payload.knowledgeReferences);
  }
  if (rawItems.length === 0) {
    rawItems = asArray(payload.knowledgeCitations);
  }
  return rawItems
    .map((item, index): ChatTimelineKnowledgeHit => {
      const source = asRecord(item);
      const metadata = asRecord(source.metadata);
      const documentName = asText(
        source.documentName ?? source.title ?? metadata.documentName,
      );
      const sourceFileName = asText(
        source.sourceFileName ?? metadata.sourceFileName,
      );
      const chunkContent = asText(
        source.chunkContent ?? source.content ?? source.text ?? source.summary,
      );
      return {
        ...source,
        id: asText(source.id ?? source.chunkId ?? index),
        chunkContent,
        content: asText(source.content ?? source.text ?? source.summary),
        documentId: asText(source.documentId ?? metadata.documentId),
        documentName,
        knowledgeId: asText(source.knowledgeId ?? payload.knowledgeId),
        knowledgeName: asText(source.knowledgeName ?? payload.knowledgeName),
        metadata,
        score: source.score ?? source.similarity,
        sourceFileName,
        sourceUri: asText(source.sourceUri ?? metadata.sourceUri),
        title: documentName || sourceFileName || asText(source.source),
      };
    })
    .filter((item) => item.chunkContent || item.title || item.documentName);
}

function normalizeImages(payload: Record<string, any>) {
  return asArray(payload.images)
    .map((value): ChatImageAttachment | undefined => {
      const image = asRecord(value);
      const previewUrl = asText(image.previewUrl);
      if (!previewUrl) return undefined;
      return {
        height: Number(image.height || 0) || undefined,
        imageRef: asText(image.imageRef),
        mimeType: asText(image.mimeType),
        name: asText(image.name) || '图片',
        previewUrl,
        size: Number(image.size || 0) || undefined,
        status: 'ready',
        width: Number(image.width || 0) || undefined,
      };
    })
    .filter((item): item is ChatImageAttachment => item !== undefined);
}

function normalizeDocuments(payload: Record<string, any>) {
  return asArray(payload.attachments)
    .map((value): ChatDocumentAttachment | undefined => {
      const document = asRecord(value);
      const attachmentRef = asText(document.attachmentRef);
      if (!attachmentRef) return undefined;
      return {
        attachmentRef,
        downloadUrl: asText(document.downloadUrl),
        mimeType: asText(document.mimeType),
        name: asText(document.name) || '文档',
        readSnapshotId: asText(document.readSnapshotId),
        size: Number(document.size || 0) || undefined,
        status: 'ready',
      };
    })
    .filter((item): item is ChatDocumentAttachment => item !== undefined);
}

function appendAssistantText(
  items: ChatTimelineItem[],
  record: AgentChatMessageRecord,
  content: unknown,
  suffix?: string,
  metadata?: Partial<ChatTimelineMessageItem>,
) {
  const text = asText(content);
  if (!text) {
    return;
  }
  ChatTimelineBuilder.appendMessageDelta(items, text, {
    ...assistantMetadata(record, suffix),
    ...metadata,
  });
}

function appendAssistantThinking(
  items: ChatTimelineItem[],
  record: AgentChatMessageRecord,
  content: unknown,
  suffix?: string,
  metadata?: Partial<ChatTimelineMessageItem>,
) {
  const text = asText(content);
  if (!text) {
    return;
  }
  ChatTimelineBuilder.appendThinkingDelta(items, text, {
    ...assistantMetadata(record, suffix),
    ...metadata,
  });
}

function projectHistoryChain(
  items: ChatTimelineItem[],
  record: AgentChatMessageRecord,
) {
  const payload = asRecord(record.contentPayload);
  let hasAssistantText = false;
  let hasAssistantThinking = false;
  const toolNameByCallId = new Map<string, string>();
  const displayChains = asArray(payload.displayChains ?? payload.chains);
  for (const chain of displayChains) {
    const item = asRecord(chain);
    const reasoning = item.reasoningContent ?? item.reasoning_content;
    if (reasoning) {
      appendAssistantThinking(items, record, reasoning, 'thinking');
      hasAssistantThinking = true;
      continue;
    }
    const toolName = normalizeToolName(
      item.toolDisplayName ?? item.name ?? item.toolName,
    );
    const toolCallId = normalizeToolCallId(item);
    if (toolCallId && toolName) {
      toolNameByCallId.set(toolCallId, toolName);
    }
    if (toolName && !shouldSkipToolProjection(toolName)) {
      ChatTimelineBuilder.upsertToolCall(items, {
        ...normalizeMetadata(record),
        input: item.arguments ?? item.input,
        output: item.result ?? item.output,
        status: asText(item.status) === 'TOOL_RESULT' ? 'success' : 'running',
        statusKey: statusKeyForProjection(
          item,
          normalizeMetadata(record),
          'knowledge-retrieval',
        ),
        toolCallId,
        toolName,
      });
    }
  }

  const messageChain = asArray(payload.messageChain);
  for (const chain of messageChain) {
    const item = asRecord(chain);
    const role = asText(item.role).toLowerCase();
    if (role === 'assistant') {
      appendAssistantThinking(items, record, item.reasoningContent, 'thinking');
      if (item.reasoningContent) {
        hasAssistantThinking = true;
      }
      if (!payload.agentResult && item.content) {
        appendAssistantText(items, record, item.content, 'text');
        hasAssistantText = true;
      }
      for (const toolCall of asArray(item.toolCalls)) {
        const tool = asRecord(toolCall);
        const toolCallId = normalizeToolCallId(tool);
        const toolName = normalizeToolCallName(tool);
        if (toolCallId && toolName) {
          toolNameByCallId.set(toolCallId, toolName);
        }
        if (isBlankToolName(toolName) || shouldSkipToolProjection(toolName)) {
          continue;
        }
        ChatTimelineBuilder.upsertToolCall(items, {
          ...normalizeMetadata(record),
          input: normalizeToolCallInput(tool),
          status: 'running',
          statusKey: statusKeyForProjection(
            tool,
            normalizeMetadata(record),
            'knowledge-retrieval',
          ),
          toolCallId,
          toolName,
        });
      }
      continue;
    }
    if (role === 'tool') {
      const toolCallId = normalizeToolCallId(item);
      const toolName =
        normalizeToolCallName(item) || toolNameByCallId.get(toolCallId) || '';
      if (isBlankToolName(toolName) || shouldSkipToolProjection(toolName)) {
        continue;
      }
      ChatTimelineBuilder.upsertToolCall(items, {
        ...normalizeMetadata(record),
        output: item.content ?? item.result,
        status: 'success',
        statusKey: statusKeyForProjection(
          item,
          normalizeMetadata(record),
          'knowledge-retrieval',
        ),
        toolCallId,
        toolName,
      });
    }
  }
  return {
    hasAssistantText,
    hasAssistantThinking,
  };
}

function appendHistoryRecord(
  items: ChatTimelineItem[],
  record: AgentChatMessageRecord,
) {
  const role = normalizeRole(record.senderRole);
  const metadata = normalizeMetadata(record);
  if (role === 'user') {
    ChatTimelineBuilder.appendUserMessage(items, record.contentText, {
      ...metadata,
      documents: normalizeDocuments(asRecord(record.contentPayload)),
      images: normalizeImages(asRecord(record.contentPayload)),
    });
    return;
  }
  if (role === 'system') {
    ChatTimelineBuilder.appendError(
      items,
      record.contentText || '系统消息',
      metadata,
    );
    return;
  }

  const payload = asRecord(record.contentPayload);
  const agentResult = asRecord(payload.agentResult);
  projectHistoryArtifacts(items, payload, metadata);
  projectHistorySkillInvocations(items, payload, metadata);
  const chainProjection = projectHistoryChain(items, record);
  if (!chainProjection.hasAssistantThinking) {
    appendAssistantThinking(
      items,
      record,
      payload.reasoningContent ?? agentResult.reasoning,
      'thinking',
    );
  }
  if (!chainProjection.hasAssistantText) {
    appendAssistantText(
      items,
      record,
      agentResult.text ?? payload.content ?? record.contentText,
      chainProjection.hasAssistantThinking ? 'text' : undefined,
    );
  }
  const knowledgeItems = normalizeKnowledgeItems({
    ...payload,
    items:
      payload.knowledgeCitations ??
      agentResult.knowledgeReferences ??
      payload.knowledgeReferences,
  });
  if (knowledgeItems.length > 0) {
    ChatTimelineBuilder.appendKnowledge(items, knowledgeItems, metadata);
  }
  const terminalStatus = asText(payload.terminalStatus).toUpperCase();
  const turnSucceeded = !terminalStatus || terminalStatus === 'COMPLETED';
  ChatTimelineBuilder.finalize(items, {
    ...metadata,
    roundCompleted: turnSucceeded,
    turnFinishedAt: turnSucceeded ? undefined : metadata.createdAt,
    turnSucceeded,
  });
}

export function recordsToTimelineItems(records: AgentChatMessageRecord[] = []) {
  const items: ChatTimelineItem[] = [];
  for (const record of records) {
    appendHistoryRecord(items, record);
  }
  ChatTimelineBuilder.finalize(items);
  applyHistoryTurnTimings(items, records);
  return items;
}
