import type {
  ChatTimeAssistantItem,
  ChatTimeHistoryRecord,
  ChatTimeRoundMeta,
  ChatTimeThinkingStatus,
  ChatTimeTimelineItem,
  ChatTimeTimelineItemBase,
  ChatTimeToolItem,
  ChatTimeToolMutationPayload,
  ChatTimeToolStatus,
} from '../../../types/src/chat-time';

import {uuid} from './uuid';

type ChatTimeToolMeta = {
  arguments?: string;
  name?: string;
};

/**
 * 聊天时间线实时构建器。
 */
class ChatTimeTimelineBuilder {
  /**
   * 追加用户消息。
   */
  static appendUserMessage(
    items: ChatTimeTimelineItem[],
    payload: {
      content?: string;
      created?: number | string;
      id?: string;
      messageKind?: string;
      roundId?: number | string;
      roundNo?: number;
      senderName?: string;
    },
  ) {
    const item: ChatTimeTimelineItem = {
      content: normalizePlainText(payload.content),
      created: normalizeTimestamp(payload.created),
      id: payload.id || uuid(),
      placement: 'end',
      role: 'user',
      senderName: payload.senderName,
    };
    applyRoundMeta(item, payload);
    items.push(item);
  }

  /**
   * 将最新一条待绑定的用户消息补齐到当前轮次。
   */
  static bindLatestPendingUserMessage(
    items: ChatTimeTimelineItem[],
    meta?: ChatTimeRoundMeta,
  ) {
    const roundId = normalizeRoundId(meta?.roundId);
    if (!roundId) {
      return;
    }
    for (let index = items.length - 1; index >= 0; index -= 1) {
      const item = items[index];
      if (!item) {
        continue;
      }
      if (item.role !== 'user') {
        continue;
      }
      if (item.roundId) {
        return;
      }
      applyRoundMeta(item, {
        roundId,
        roundNo: meta?.roundNo,
      });
      return;
    }
  }

  /**
   * 更新指定轮次的可切换状态。
   */
  static setRoundSwitchable(
    items: ChatTimeTimelineItem[],
    roundId: number | string | undefined,
    switchable: boolean,
  ) {
    const normalizedRoundId = normalizeRoundId(roundId);
    if (!normalizedRoundId) {
      return;
    }
    for (const item of items) {
      if (item.roundId === normalizedRoundId && item.role !== 'user') {
        item.switchable = switchable;
      }
    }
  }

  /**
   * 追加思考增量。
   */
  static appendThinkingDelta(
    items: ChatTimeTimelineItem[],
    delta?: string,
    created?: number | string,
    meta?: ChatTimeRoundMeta,
  ) {
    const normalizedDelta = normalizePlainText(delta);
    if (!normalizedDelta) {
      return;
    }
    prepareRoundVariant(items, meta);
    const assistant = ensureAssistantTail(items, created, meta);
    const tail = assistant.segments[assistant.segments.length - 1];
    if (tail?.type === 'thinking' && tail.status === 'thinking') {
      tail.content += normalizedDelta;
    } else {
      assistant.segments.push({
        content: normalizedDelta,
        expanded: false,
        id: uuid(),
        status: 'thinking',
        type: 'thinking',
      });
    }
    assistant.loading = true;
    assistant.typing = true;
  }

  /**
   * 追加回答增量。
   */
  static appendMessageDelta(
    items: ChatTimeTimelineItem[],
    delta?: string,
    created?: number | string,
    meta?: ChatTimeRoundMeta,
  ) {
    const normalizedDelta = normalizeAssistantText(delta);
    if (!normalizedDelta) {
      return;
    }
    prepareRoundVariant(items, meta);
    const assistant = ensureAssistantTail(items, created, meta);
    stopThinkingForAssistant(assistant);
    const tail = assistant.segments[assistant.segments.length - 1];
    if (tail?.type === 'text') {
      tail.content += normalizedDelta;
    } else {
      assistant.segments.push({
        content: normalizedDelta,
        id: uuid(),
        type: 'text',
      });
    }
    assistant.content += normalizedDelta;
    assistant.loading = false;
    assistant.typing = true;
  }

  /**
   * 用最终完整回答替换当前 assistant 文本。
   */
  static replaceMessageContent(
    items: ChatTimeTimelineItem[],
    content?: string,
    created?: number | string,
    meta?: ChatTimeRoundMeta,
  ) {
    const normalizedContent = normalizeAssistantText(content);
    if (!normalizedContent) {
      return;
    }
    prepareRoundVariant(items, meta);
    const assistant = ensureAssistantTail(items, created, meta);
    stopThinkingForAssistant(assistant);
    assistant.content = normalizedContent;
    assistant.segments = [
      ...assistant.segments.filter((segment) => segment.type !== 'text'),
      {
        content: normalizedContent,
        id: uuid(),
        type: 'text' as const,
      },
    ];
    assistant.loading = false;
    assistant.typing = false;
  }

  /**
   * 停止当前 assistant 的思考态。
   */
  static stopThinking(items: ChatTimeTimelineItem[]) {
    const last = items[items.length - 1];
    if (!isAssistantItem(last)) {
      return;
    }
    stopThinkingForAssistant(last);
  }

  /**
   * 更新工具调用状态。
   */
  static upsertToolCall(
    items: ChatTimeTimelineItem[],
    payload: ChatTimeToolMutationPayload,
  ) {
    prepareRoundVariant(items, payload);
    this.stopThinking(items);
    const toolItem = ensureToolItem(
      items,
      payload.toolCallId,
      payload.created,
      payload.name,
      payload,
    );
    toolItem.arguments = normalizePayloadValue(payload.value);
    toolItem.content = '';
    toolItem.status = 'TOOL_CALL';
  }

  /**
   * 更新工具结果状态。
   */
  static upsertToolResult(
    items: ChatTimeTimelineItem[],
    payload: ChatTimeToolMutationPayload,
  ) {
    prepareRoundVariant(items, payload);
    const toolItem = ensureToolItem(
      items,
      payload.toolCallId,
      payload.created,
      payload.name,
      payload,
    );
    toolItem.result = normalizePayloadValue(payload.result);
    toolItem.content = toolItem.result;
    toolItem.status = 'TOOL_RESULT';
  }

  /**
   * 追加系统错误。
   */
  static applySystemError(
    items: ChatTimeTimelineItem[],
    message?: string,
    created?: number | string,
  ) {
    const errorMessage = normalizePlainText(message);
    if (!errorMessage) {
      return;
    }
    const last = items[items.length - 1];
    if (isAssistantItem(last)) {
      stopThinkingForAssistant(last);
      appendAssistantText(last, errorMessage);
      last.loading = false;
      last.typing = false;
      return;
    }
    const assistant = createAssistantItem(created);
    appendAssistantText(assistant, errorMessage);
    assistant.loading = false;
    assistant.typing = false;
    items.push(assistant);
  }

  /**
   * 结束当前轮的 assistant 状态。
   */
  static finalize(items: ChatTimeTimelineItem[]) {
    const last = findLastAssistant(items);
    if (!isAssistantItem(last)) {
      return;
    }
    stopThinkingForAssistant(last);
    last.loading = false;
    last.typing = false;
  }

  /**
   * 按轮次替换当前主线可见的 assistant/tool 片段。
   */
  static replaceRoundMessages(
    items: ChatTimeTimelineItem[],
    roundId: number | string | undefined,
    nextMessages: ChatTimeTimelineItem[],
  ) {
    const normalizedRoundId = normalizeRoundId(roundId);
    if (!normalizedRoundId) {
      return;
    }
    const range = resolveRoundReplaceRange(items, normalizedRoundId);
    if (range) {
      items.splice(range.start, range.deleteCount, ...nextMessages);
      return;
    }
    items.splice(resolveRoundInsertIndex(items, normalizedRoundId), 0, ...nextMessages);
  }
}

/**
 * 聊天时间线历史映射器。
 */
class ChatTimeHistoryMapper {
  /**
   * 从聊天历史记录恢复时间线。
   */
  static fromHistoryRecords(records: ChatTimeHistoryRecord[]) {
    return normalizeVisibleHistoryRecords(records).flatMap((record) =>
      this.fromHistoryRecord(record),
    );
  }

  /**
   * 从旧版消息列表恢复时间线。
   */
  static fromLegacyMessages(records: ChatTimeHistoryRecord[]) {
    return records.flatMap((record) => this.fromLegacyRecord(record));
  }

  private static fromHistoryRecord(record: ChatTimeHistoryRecord) {
    const role = normalizeRole(record.senderRole || record.role);
    if (role === 'user') {
      return [createUserItem(record)];
    }
    if (role === 'tool') {
      return [createToolItemFromTopLevelRecord(record)];
    }
    if (role !== 'assistant') {
      return [];
    }

    const payload = toObjectRecord(record.contentPayload);
    const messageChain = toObjectArray(payload.messageChain);
    if (messageChain.length > 0) {
      const structuredItems = this.fromStructuredAssistantRecord(
        record,
        messageChain,
      );
      if (structuredItems.length > 0) {
        return structuredItems;
      }
    }
    return this.fromLegacyRecord(record);
  }

  private static fromLegacyRecord(record: ChatTimeHistoryRecord) {
    const role = normalizeRole(record.senderRole || record.role);
    if (role === 'user') {
      return [createUserItem(record)];
    }
    if (role === 'tool') {
      return [createToolItemFromTopLevelRecord(record)];
    }
    if (role !== 'assistant') {
      return [];
    }

    const content = normalizeAssistantText(record.contentText || record.content);
    const chains = extractDisplayChains(record);
    const assistant = createAssistantItem(record.created, {
      id: record.id == null ? undefined : String(record.id),
      loading: record.loading,
      messageKind: record.messageKind,
      roundId: normalizeRoundId(record.roundId),
      roundNo: record.roundNo,
      selectedVariantIndex: record.selectedVariantIndex,
      senderName: record.senderName,
      switchable: record.switchable,
      typing: record.typing,
      variantCount: record.variantCount,
      variantIndex: record.variantIndex,
    });
    const tools: ChatTimeTimelineItem[] = [];

    for (const rawChain of chains) {
      const reasoning = normalizePlainText(rawChain.reasoning_content);
      if (reasoning) {
        assistant.segments.push({
          content: reasoning,
          expanded: Boolean(rawChain.thinkingExpanded),
          id: uuid(),
          status: normalizeThinkingStatus(rawChain.thinkingStatus),
          type: 'thinking',
        });
        continue;
      }

      const toolItem = createToolItemFromChain(rawChain, record.created, record);
      if (toolItem) {
        tools.push(toolItem);
      }
    }

    if (content) {
      appendAssistantText(assistant, content);
    }

    const results: ChatTimeTimelineItem[] = [];
    if (assistant.segments.length > 0) {
      assistant.loading = false;
      assistant.typing = false;
      results.push(assistant);
    }
    return [...results, ...tools];
  }

  private static fromStructuredAssistantRecord(
    record: ChatTimeHistoryRecord,
    messageChain: Array<Record<string, any>>,
  ) {
    const toolMetaMap = new Map<string, ChatTimeToolMeta>();
    const items: ChatTimeTimelineItem[] = [];
    let assistantIndex = 0;

    for (const rawMessage of messageChain) {
      const role = normalizeRole(rawMessage.role);
      if (role === 'assistant') {
        collectToolMeta(rawMessage, toolMetaMap);
        const assistant = createAssistantItemFromStructuredMessage(
          rawMessage,
          record,
          assistantIndex,
        );
        if (assistant) {
          items.push(assistant);
          assistantIndex += 1;
        }
        continue;
      }

      if (role === 'tool') {
        items.push(
          createToolItemFromStructuredMessage(
            rawMessage,
            toolMetaMap,
            record.created,
            record,
          ),
        );
      }
    }

    return items;
  }
}

function normalizeVisibleHistoryRecords(records: ChatTimeHistoryRecord[]) {
  const dedupedRecords = dedupeHistoryRecords(records);
  const userSelectedVariantByRound = new Map<string, number>();
  const assistantSelectedVariantByRound = new Map<string, number>();
  const fallbackVariantByRound = new Map<string, number>();
  for (const record of dedupedRecords) {
    const roundId = normalizeRoundId(record.roundId);
    if (!roundId) {
      continue;
    }
    const selectedVariantIndex = normalizePositiveInteger(
      record.selectedVariantIndex,
    );
    if (selectedVariantIndex) {
      if (isUserHistoryRecord(record)) {
        userSelectedVariantByRound.set(roundId, selectedVariantIndex);
      } else {
        assistantSelectedVariantByRound.set(roundId, selectedVariantIndex);
      }
    }
    const variantIndex = normalizePositiveInteger(record.variantIndex);
    if (!isUserHistoryRecord(record) && variantIndex) {
      fallbackVariantByRound.set(roundId, variantIndex);
    }
  }
  return dedupedRecords.filter((record) => {
    const roundId = normalizeRoundId(record.roundId);
    if (!roundId || isUserHistoryRecord(record)) {
      return true;
    }
    const variantIndex = normalizePositiveInteger(record.variantIndex);
    if (!variantIndex) {
      return true;
    }
    const selectedVariantIndex =
      userSelectedVariantByRound.get(roundId) ||
      assistantSelectedVariantByRound.get(roundId) ||
      fallbackVariantByRound.get(roundId);
    return !selectedVariantIndex || variantIndex === selectedVariantIndex;
  });
}

function dedupeHistoryRecords(records: ChatTimeHistoryRecord[]) {
  const seen = new Set<string>();
  const result: ChatTimeHistoryRecord[] = [];
  for (const record of records) {
    const key = resolveHistoryRecordKey(record);
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    result.push(record);
  }
  return result;
}

function resolveHistoryRecordKey(record: ChatTimeHistoryRecord) {
  if (record.id != null) {
    return `id:${String(record.id)}`;
  }
  return [
    'fallback',
    normalizeRoundId(record.roundId) || '',
    normalizeRole(record.senderRole || record.role),
    normalizePositiveInteger(record.variantIndex) || '',
    normalizePlainText(record.contentText || record.content),
  ].join(':');
}

function isUserHistoryRecord(record: ChatTimeHistoryRecord) {
  return normalizeRole(record.senderRole || record.role) === 'user';
}

function createAssistantItem(
  created?: number | string,
  patch?: Omit<Partial<ChatTimeAssistantItem>, 'roundId'> & ChatTimeRoundMeta,
): ChatTimeAssistantItem {
  const item: ChatTimeAssistantItem = {
    content: patch?.content || '',
    created: normalizeTimestamp(created),
    id: patch?.id || uuid(),
    loading: patch?.loading,
    placement: 'start',
    role: 'assistant',
    segments: patch?.segments ? [...patch.segments] : [],
    senderName: patch?.senderName,
    typing: patch?.typing,
  };
  applyRoundMeta(item, patch);
  return item;
}

function createAssistantItemFromStructuredMessage(
  rawMessage: Record<string, any>,
  record: ChatTimeHistoryRecord,
  assistantIndex: number,
) {
  const content = normalizeAssistantText(rawMessage.content);
  const reasoning = normalizePlainText(
    rawMessage.reasoningContent ?? rawMessage.reasoning_content,
  );
  if (!content && !reasoning) {
    return null;
  }
  const assistant = createAssistantItem(record.created, {
    id:
      record.id == null
        ? undefined
        : `${String(record.id)}-assistant-${assistantIndex}`,
    loading: false,
    messageKind: record.messageKind,
    roundId: normalizeRoundId(record.roundId),
    roundNo: record.roundNo,
    selectedVariantIndex: record.selectedVariantIndex,
    senderName: record.senderName,
    switchable: record.switchable,
    typing: false,
    variantCount: record.variantCount,
    variantIndex: record.variantIndex,
  });
  if (reasoning) {
    assistant.segments.push({
      content: reasoning,
      expanded: false,
      id: uuid(),
      status: 'end',
      type: 'thinking',
    });
  }
  if (content) {
    appendAssistantText(assistant, content);
  }
  return assistant;
}

function createToolItemFromChain(
  rawChain: Record<string, any>,
  created?: number | string,
  record?: ChatTimeHistoryRecord,
) {
  const toolCallId = normalizePlainText(rawChain.id);
  const name = normalizePlainText(rawChain.name);
  const argumentsValue = normalizePayloadValue(rawChain.arguments ?? rawChain.result);
  const status = normalizeToolStatus(rawChain.status);
  if (!toolCallId && !name && !argumentsValue) {
    return null;
  }
  return createToolItem({
    arguments: status === 'TOOL_CALL' ? argumentsValue : undefined,
    created,
    id: toolCallId || uuid(),
    messageKind: record?.messageKind,
    name,
    roundId: record?.roundId,
    roundNo: record?.roundNo,
    result: status === 'TOOL_RESULT' ? argumentsValue : undefined,
    selectedVariantIndex: record?.selectedVariantIndex,
    status,
    switchable: record?.switchable,
    toolCallId,
    variantCount: record?.variantCount,
    variantIndex: record?.variantIndex,
  });
}

function createToolItemFromStructuredMessage(
  rawMessage: Record<string, any>,
  toolMetaMap: Map<string, ChatTimeToolMeta>,
  created?: number | string,
  record?: ChatTimeHistoryRecord,
) {
  const toolCallId = normalizePlainText(
    rawMessage.toolCallId ?? rawMessage.tool_call_id,
  );
  const toolMeta = toolMetaMap.get(toolCallId);
  const result = normalizePayloadValue(rawMessage.content);
  return createToolItem({
    arguments: toolMeta?.arguments,
    created,
    id: toolCallId || uuid(),
    messageKind: record?.messageKind,
    name: toolMeta?.name,
    roundId: record?.roundId,
    roundNo: record?.roundNo,
    result,
    selectedVariantIndex: record?.selectedVariantIndex,
    status: 'TOOL_RESULT',
    switchable: record?.switchable,
    toolCallId,
    variantCount: record?.variantCount,
    variantIndex: record?.variantIndex,
  });
}

function createToolItemFromTopLevelRecord(record: ChatTimeHistoryRecord) {
  const payload = toObjectRecord(record.contentPayload);
  const toolCallId = normalizePlainText(
    payload.toolCallId ?? payload.tool_call_id ?? record.id,
  );
  return createToolItem({
    created: record.created,
    id: record.id == null ? toolCallId || uuid() : String(record.id),
    messageKind: record.messageKind,
    name: normalizePlainText(payload.name),
    roundId: record.roundId,
    roundNo: record.roundNo,
    result: normalizePayloadValue(
      payload.content ?? payload.result ?? record.contentText ?? record.content,
    ),
    selectedVariantIndex: record.selectedVariantIndex,
    status: 'TOOL_RESULT',
    switchable: record.switchable,
    toolCallId,
    variantCount: record.variantCount,
    variantIndex: record.variantIndex,
  });
}

function createToolItem(payload: {
  arguments?: string;
  created?: number | string;
  id?: string;
  messageKind?: string;
  name?: string;
  roundId?: number | string;
  roundNo?: number;
  result?: string;
  selectedVariantIndex?: number;
  status: ChatTimeToolStatus;
  switchable?: boolean;
  toolCallId?: string;
  variantCount?: number;
  variantIndex?: number;
}): ChatTimeToolItem {
  const item: ChatTimeToolItem = {
    arguments: payload.arguments,
    content: payload.result || '',
    created: normalizeTimestamp(payload.created),
    id: payload.id || payload.toolCallId || uuid(),
    name: payload.name || '',
    placement: 'start',
    result: payload.result,
    role: 'tool',
    status: payload.status,
    toolCallId: payload.toolCallId || payload.id || uuid(),
  };
  applyRoundMeta(item, payload);
  return item;
}

function createUserItem(record: ChatTimeHistoryRecord): ChatTimeTimelineItem {
  const item: ChatTimeTimelineItem = {
    content: normalizePlainText(record.contentText || record.content),
    created: normalizeTimestamp(record.created),
    id: record.id == null ? uuid() : String(record.id),
    loading: record.loading,
    placement: record.placement || 'end',
    role: 'user',
    senderName: record.senderName,
    typing: record.typing,
  };
  applyRoundMeta(item, record);
  return item;
}

function appendAssistantText(item: ChatTimeAssistantItem, content: string) {
  const normalized = normalizeAssistantText(content);
  if (!normalized) {
    return;
  }
  item.content += normalized;
  item.segments.push({
    content: normalized,
    id: uuid(),
    type: 'text',
  });
}

function collectToolMeta(
  rawMessage: Record<string, any>,
  toolMetaMap: Map<string, ChatTimeToolMeta>,
) {
  const toolCalls = toObjectArray(rawMessage.toolCalls ?? rawMessage.tool_calls);
  for (const toolCall of toolCalls) {
    const toolCallId = normalizePlainText(toolCall.id);
    if (!toolCallId) {
      continue;
    }
    toolMetaMap.set(toolCallId, {
      arguments: normalizePayloadValue(toolCall.arguments),
      name: normalizePlainText(toolCall.name),
    });
  }
}

function ensureAssistantTail(
  items: ChatTimeTimelineItem[],
  created?: number | string,
  meta?: ChatTimeRoundMeta,
) {
  const last = items[items.length - 1];
  if (isAssistantItem(last) && isSameRoundVariant(last, meta)) {
    applyRoundMeta(last, meta);
    return last;
  }
  const assistant = createAssistantItem(created, {
    loading: true,
    typing: true,
    ...normalizeRoundMeta(meta),
  });
  items.push(assistant);
  return assistant;
}

function ensureToolItem(
  items: ChatTimeTimelineItem[],
  toolCallId?: string,
  created?: number | string,
  name?: string,
  meta?: ChatTimeRoundMeta,
) {
  const normalizedToolCallId = normalizePlainText(toolCallId);
  const found = findToolItem(items, normalizedToolCallId, meta);
  if (found) {
    if (name) {
      found.name = name;
    }
    applyRoundMeta(found, meta);
    return found;
  }
  const toolItem = createToolItem({
    created,
    id: normalizedToolCallId || uuid(),
    messageKind: meta?.messageKind,
    name,
    roundId: meta?.roundId,
    roundNo: meta?.roundNo,
    selectedVariantIndex: meta?.selectedVariantIndex,
    status: 'TOOL_CALL',
    switchable: meta?.switchable,
    toolCallId: normalizedToolCallId,
    variantCount: meta?.variantCount,
    variantIndex: meta?.variantIndex,
  });
  items.push(toolItem);
  return toolItem;
}

function findToolItem(
  items: ChatTimeTimelineItem[],
  toolCallId?: string,
  meta?: ChatTimeRoundMeta,
) {
  const normalizedRoundId = normalizeRoundId(meta?.roundId);
  const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex);
  if (toolCallId) {
    for (let index = items.length - 1; index >= 0; index -= 1) {
      const item = items[index];
      if (!item) {
        continue;
      }
      if (
        isToolItem(item) &&
        item.toolCallId === toolCallId &&
        matchesRoundVariant(item, normalizedRoundId, normalizedVariantIndex)
      ) {
        return item;
      }
    }
  }
  for (let index = items.length - 1; index >= 0; index -= 1) {
    const item = items[index];
    if (!item) {
      continue;
    }
    if (
      isToolItem(item) &&
      item.status === 'TOOL_CALL' &&
      matchesRoundVariant(item, normalizedRoundId, normalizedVariantIndex)
    ) {
      return item;
    }
  }
  return null;
}

function extractDisplayChains(record: ChatTimeHistoryRecord) {
  if (Array.isArray(record.chains)) {
    return record.chains.map((item) => toObjectRecord(item));
  }
  const payload = toObjectRecord(record.contentPayload);
  if (Array.isArray(payload.chains)) {
    return payload.chains.map((item: unknown) => toObjectRecord(item));
  }
  return [];
}

function isAssistantItem(
  item?: ChatTimeTimelineItem,
): item is ChatTimeAssistantItem {
  return item?.role === 'assistant';
}

function isToolItem(item?: ChatTimeTimelineItem): item is ChatTimeToolItem {
  return item?.role === 'tool';
}

function findLastAssistant(items: ChatTimeTimelineItem[]) {
  for (let index = items.length - 1; index >= 0; index -= 1) {
    const item = items[index];
    if (isAssistantItem(item)) {
      return item;
    }
  }
  return undefined;
}

function prepareRoundVariant(
  items: ChatTimeTimelineItem[],
  meta?: ChatTimeRoundMeta,
) {
  const normalizedRoundId = normalizeRoundId(meta?.roundId);
  const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex);
  if (!normalizedRoundId || !normalizedVariantIndex) {
    return;
  }
  const assistant = items.find(
    (item) => item.role === 'assistant' && item.roundId === normalizedRoundId,
  );
  if (assistant?.variantIndex === normalizedVariantIndex) {
    return;
  }
  for (let index = items.length - 1; index >= 0; index -= 1) {
    const item = items[index];
    if (!item) {
      continue;
    }
    if (item.roundId === normalizedRoundId && item.role !== 'user') {
      items.splice(index, 1);
    }
  }
}

function resolveRoundInsertIndex(
  items: ChatTimeTimelineItem[],
  roundId: string,
) {
  const firstRoundItemIndex = items.findIndex(
    (item) => item.roundId === roundId && item.role !== 'user',
  );
  if (firstRoundItemIndex >= 0) {
    return firstRoundItemIndex;
  }
  for (let index = items.length - 1; index >= 0; index -= 1) {
    const item = items[index];
    if (!item) {
      continue;
    }
    if (item.roundId === roundId && item.role === 'user') {
      return index + 1;
    }
  }
  return items.length;
}

function resolveRoundReplaceRange(
  items: ChatTimeTimelineItem[],
  roundId: string,
) {
  let start = -1;
  let end = -1;
  for (let index = 0; index < items.length; index += 1) {
    const item = items[index];
    if (item?.roundId === roundId && item.role !== 'user') {
      if (start < 0) {
        start = index;
      }
      end = index;
    } else if (start >= 0) {
      break;
    }
  }
  if (start < 0) {
    return null;
  }
  return {
    deleteCount: end - start + 1,
    start,
  };
}

function matchesRoundVariant(
  item: ChatTimeTimelineItem,
  roundId?: string,
  variantIndex?: number,
) {
  if (roundId && item.roundId !== roundId) {
    return false;
  }
  if (variantIndex && item.variantIndex && item.variantIndex !== variantIndex) {
    return false;
  }
  return true;
}

function isSameRoundVariant(
  item: ChatTimeTimelineItem,
  meta?: ChatTimeRoundMeta,
) {
  const normalizedRoundId = normalizeRoundId(meta?.roundId);
  const normalizedVariantIndex = normalizePositiveInteger(meta?.variantIndex);
  if (!normalizedRoundId || !normalizedVariantIndex) {
    return true;
  }
  return (
    item.roundId === normalizedRoundId &&
    normalizePositiveInteger(item.variantIndex) === normalizedVariantIndex
  );
}

function applyRoundMeta(
  target: Partial<ChatTimeTimelineItemBase>,
  source?: ChatTimeRoundMeta | null,
) {
  if (!source) {
    return;
  }
  const roundId = normalizeRoundId(source.roundId);
  if (roundId) {
    target.roundId = roundId;
  }
  const roundNo = normalizePositiveInteger(source.roundNo);
  if (roundNo) {
    target.roundNo = roundNo;
  }
  const variantIndex = normalizePositiveInteger(source.variantIndex);
  if (variantIndex) {
    target.variantIndex = variantIndex;
  }
  const variantCount = normalizePositiveInteger(source.variantCount);
  if (variantCount) {
    target.variantCount = variantCount;
  }
  const selectedVariantIndex = normalizePositiveInteger(
    source.selectedVariantIndex,
  );
  if (selectedVariantIndex) {
    target.selectedVariantIndex = selectedVariantIndex;
  }
  if (typeof source.switchable === 'boolean') {
    target.switchable = source.switchable;
  }
  const messageKind = normalizePlainText(source.messageKind).trim();
  if (messageKind) {
    target.messageKind = messageKind;
  }
}

function normalizeRoundMeta(meta?: ChatTimeRoundMeta): ChatTimeRoundMeta {
  return {
    messageKind: meta?.messageKind,
    roundId: normalizeRoundId(meta?.roundId),
    roundNo: meta?.roundNo,
    selectedVariantIndex: meta?.selectedVariantIndex,
    switchable: meta?.switchable,
    variantCount: meta?.variantCount,
    variantIndex: meta?.variantIndex,
  };
}

function normalizeRoundId(value: any) {
  const normalized = normalizePlainText(value).trim();
  return normalized || undefined;
}

function normalizePositiveInteger(value: any) {
  if (value == null || value === '') {
    return undefined;
  }
  const parsed = Number.parseInt(String(value), 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
}

function normalizeAssistantText(value: any) {
  return normalizePlainText(value);
}

function normalizePayloadValue(value: any) {
  if (value == null) {
    return '';
  }
  if (typeof value === 'string') {
    return value;
  }
  try {
    return JSON.stringify(value, null, 2);
  } catch {
    return String(value);
  }
}

function normalizePlainText(value: any) {
  return typeof value === 'string' ? value : value == null ? '' : String(value);
}

function normalizeRole(value: any) {
  return normalizePlainText(value).trim().toLowerCase();
}

function normalizeThinkingStatus(value: any): ChatTimeThinkingStatus {
  return normalizeRole(value) === 'thinking' ? 'thinking' : 'end';
}

function normalizeTimestamp(value?: number | string) {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value;
  }
  if (typeof value === 'string' && value.trim()) {
    const parsed = Date.parse(value);
    return Number.isNaN(parsed) ? value : parsed;
  }
  return Date.now();
}

function normalizeToolStatus(value: any): ChatTimeToolStatus {
  return normalizePlainText(value).toUpperCase() === 'TOOL_CALL'
    ? 'TOOL_CALL'
    : 'TOOL_RESULT';
}

function stopThinkingForAssistant(item: ChatTimeAssistantItem) {
  item.segments = item.segments.map(
    (segment: ChatTimeAssistantItem['segments'][number]) => {
      if (segment.type !== 'thinking' || segment.status !== 'thinking') {
        return segment;
      }
      return {
        ...segment,
        status: 'end' as ChatTimeThinkingStatus,
      };
    },
  );
}

function toObjectArray(value: any): Array<Record<string, any>> {
  if (!Array.isArray(value)) {
    return [];
  }
  return value
    .map((item: unknown) => toObjectRecord(item))
    .filter((item) => Object.keys(item).length > 0);
}

function toObjectRecord(value: any): Record<string, any> {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return {};
  }
  return value as Record<string, any>;
}

export { ChatTimeHistoryMapper, ChatTimeTimelineBuilder };
