import type {
  ChatTimelineItem,
  ChatTimelineKnowledgeHit,
  ChatTimelineMessageItem,
  ChatTimelineMessagePart,
  ChatTimelineStatusItem,
  ChatTimelineStatusStatus,
  ChatTimelineStatusTone,
  ChatTimelineThinkingStatus,
  ChatTimelineToolApprovalPayload,
  ChatTimelineToolItem,
  ChatTimelineToolMode,
  ChatTimelineToolStatus,
} from './types';

function createId(prefix: string) {
  return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}

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

function normalizePayloadValue(value: unknown) {
  if (value === null || value === undefined || value === '') {
    return undefined;
  }
  if (typeof value === 'string') {
    return value;
  }
  return value;
}

function normalizeToolName(value?: string) {
  return normalizeText(value).trim().toLowerCase();
}

function isHiddenToolName(toolName?: string) {
  const normalizedName = normalizeToolName(toolName);
  return (
    normalizedName === 'retrieve_knowledge' ||
    normalizedName === 'context_reload' ||
    normalizedName === '__fragment__'
  );
}

function isKnowledgeRetrievalToolName(toolName?: string) {
  return normalizeToolName(toolName) === 'retrieve_knowledge';
}

function isBlankToolName(toolName?: string) {
  return !normalizeToolName(toolName);
}

function knowledgeRetrievalStatusKey(statusKey?: string) {
  return normalizeText(statusKey).trim() || 'knowledge-retrieval';
}

function ensureMessageTail(
  items: ChatTimelineItem[],
  role: ChatTimelineMessageItem['role'],
  status: ChatTimelineMessageItem['status'] = 'streaming',
  metadata?: Partial<ChatTimelineMessageItem>,
) {
  const last = items[items.length - 1];
  if (
    last?.type === 'message' &&
    last.role === role &&
    last.status !== 'done' &&
    (!metadata?.roundId || last.roundId === metadata.roundId)
  ) {
    last.status = status;
    Object.assign(last, metadata);
    return last;
  }
  const item: ChatTimelineMessageItem = {
    id: createId(role),
    role,
    status,
    createdAt: Date.now(),
    parts: [],
    type: 'message',
    ...metadata,
  };
  items.push(item);
  return item;
}

function appendMessagePart(
  message: ChatTimelineMessageItem,
  part: ChatTimelineMessagePart,
) {
  const tail = message.parts[message.parts.length - 1];
  if (tail?.type === part.type) {
    tail.content += part.content;
    if (tail.type === 'thinking' && part.type === 'thinking') {
      tail.status = part.status;
    }
    return;
  }
  message.parts.push(part);
}

function appendThinkingPart(
  message: ChatTimelineMessageItem,
  part: Extract<ChatTimelineMessagePart, { type: 'thinking' }>,
) {
  appendMessagePart(message, part);
}

function appendTextPart(message: ChatTimelineMessageItem, content: string) {
  appendMessagePart(message, {
    id: createId('text'),
    content,
    type: 'text',
  });
}

function replaceTextPart(message: ChatTimelineMessageItem, content: string) {
  message.parts = [
    ...message.parts.filter((part) => part.type !== 'text'),
    {
      id: createId('text'),
      content,
      type: 'text' as const,
    },
  ];
}

function updateThinkingStatus(
  message: ChatTimelineMessageItem,
  status: ChatTimelineThinkingStatus,
) {
  message.parts = message.parts.map((part) =>
    part.type === 'thinking' && part.status === 'thinking'
      ? { ...part, expanded: status === 'thinking', status }
      : part,
  );
}

function finishLastAssistantMessage(items: ChatTimelineItem[]) {
  finishAssistantMessage(items, true);
}

function finishAssistantMessage(
  items: ChatTimelineItem[],
  roundCompleted: boolean,
) {
  const lastMessage = [...items]
    .reverse()
    .find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' && item.role === 'assistant',
    );
  if (!lastMessage) {
    return;
  }
  updateThinkingStatus(lastMessage, 'end');
  lastMessage.status = lastMessage.status === 'error' ? 'error' : 'done';
  if (lastMessage.status === 'done' && roundCompleted) {
    lastMessage.roundCompleted = true;
  }
}

function findToolItem(items: ChatTimelineItem[], toolCallId?: string) {
  const identity = normalizeText(toolCallId).trim();
  if (!identity) {
    return undefined;
  }
  return items.find(
    (item): item is ChatTimelineToolItem =>
      item.type === 'tool' && item.toolCallId === identity,
  );
}

function findStatusItem(items: ChatTimelineItem[], statusKey: string) {
  return items.find(
    (item): item is ChatTimelineStatusItem =>
      item.type === 'status' && item.statusKey === statusKey,
  );
}

function removeStatusItem(items: ChatTimelineItem[], statusKey: string) {
  const index = items.findIndex(
    (item) => item.type === 'status' && item.statusKey === statusKey,
  );
  if (index >= 0) {
    items.splice(index, 1);
  }
}

function doneStatusLabel(item: ChatTimelineStatusItem) {
  if (item.statusKey === 'knowledge-retrieval') {
    return '已检索知识库';
  }
  if (item.statusKey === 'memory-compression') {
    return '已整理上下文';
  }
  return item.label.replace(/^正在/, '已');
}

function finishRunningStatusItems(items: ChatTimelineItem[]) {
  items.forEach((item) => {
    if (item.type !== 'status' || item.status !== 'running') {
      return;
    }
    item.status = 'done';
    item.label = doneStatusLabel(item);
  });
}

function upsertStatus(
  items: ChatTimelineItem[],
  payload: {
    label: string;
    presentation?: ChatTimelineStatusItem['presentation'];
    status: ChatTimelineStatusStatus;
    statusKey: string;
    tone?: ChatTimelineStatusTone;
  },
) {
  const found = findStatusItem(items, payload.statusKey);
  if (found) {
    found.label = payload.label;
    found.presentation = payload.presentation ?? found.presentation;
    found.status = payload.status;
    found.tone = payload.tone ?? found.tone;
    return found;
  }
  const item: ChatTimelineStatusItem = {
    id: payload.statusKey,
    createdAt: Date.now(),
    label: payload.label,
    presentation: payload.presentation,
    status: payload.status,
    statusKey: payload.statusKey,
    tone: payload.tone ?? 'muted',
    type: 'status',
  };
  items.push(item);
  return item;
}

function upsertTool(
  items: ChatTimelineItem[],
  payload: {
    approval?: ChatTimelineToolApprovalPayload;
    input?: unknown;
    mode?: ChatTimelineToolMode;
    output?: unknown;
    rejectReason?: string;
    requestId?: string;
    resumeToken?: string;
    status?: ChatTimelineToolStatus;
    toolCallId?: string;
    toolName?: string;
  },
) {
  const toolCallId = normalizeText(
    payload.toolCallId ?? payload.approval?.toolCallId,
  ).trim();
  const found = findToolItem(items, toolCallId);
  const approval = payload.approval ?? found?.approval;
  const mode =
    payload.mode === 'approval'
      ? 'approval'
      : (found?.mode ?? payload.mode ?? (approval ? 'approval' : 'auto'));
  const toolName =
    payload.toolName ||
    approval?.toolDisplayName ||
    approval?.toolName ||
    found?.toolName;
  if (isHiddenToolName(toolName)) {
    return found;
  }
  if (!found && isBlankToolName(toolName)) {
    return undefined;
  }

  if (found) {
    found.approval = approval;
    found.input =
      normalizePayloadValue(payload.input) ??
      normalizePayloadValue(approval?.input) ??
      found.input;
    found.mode = mode;
    found.output = normalizePayloadValue(payload.output) ?? found.output;
    found.rejectReason = payload.rejectReason ?? found.rejectReason;
    found.status = payload.status || found.status;
    found.toolCallId = toolCallId || found.toolCallId;
    found.toolName = toolName || found.toolName;
    return found;
  }

  const toolItem: ChatTimelineToolItem = {
    id: toolCallId || createId('tool'),
    approval,
    createdAt: Date.now(),
    input:
      normalizePayloadValue(payload.input) ??
      normalizePayloadValue(approval?.input),
    mode,
    output: normalizePayloadValue(payload.output),
    rejectReason: payload.rejectReason,
    status:
      payload.status || (mode === 'approval' ? 'pending_approval' : 'running'),
    toolCallId,
    toolName: toolName || '工具调用',
    type: 'tool',
  };
  items.push(toolItem);
  return toolItem;
}

export const ChatTimelineBuilder = {
  appendUserMessage(
    items: ChatTimelineItem[],
    content?: unknown,
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    const text = normalizeText(content);
    if (!text && !metadata?.images?.length) {
      return;
    }
    const item: ChatTimelineMessageItem = {
      id: createId('user'),
      role: 'user',
      status: 'done',
      createdAt: Date.now(),
      parts: text
        ? [
            {
              id: createId('text'),
              content: text,
              type: 'text' as const,
            },
          ]
        : [],
      type: 'message',
      ...metadata,
    };
    items.push(item);
  },

  appendThinkingDelta(
    items: ChatTimelineItem[],
    delta?: unknown,
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    const text = normalizeText(delta);
    if (!text) {
      return;
    }
    const message = ensureMessageTail(
      items,
      'assistant',
      'streaming',
      metadata,
    );
    if (message.parts.some((part) => part.type === 'text')) {
      return;
    }
    appendThinkingPart(message, {
      id: createId('thinking'),
      content: text,
      expanded: true,
      status: 'thinking',
      type: 'thinking',
    });
  },

  appendMessageDelta(
    items: ChatTimelineItem[],
    delta?: unknown,
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    const text = normalizeText(delta);
    if (!text) {
      return;
    }
    const message = ensureMessageTail(
      items,
      'assistant',
      'streaming',
      metadata,
    );
    updateThinkingStatus(message, 'end');
    appendTextPart(message, text);
  },

  replaceMessageContent(items: ChatTimelineItem[], content?: unknown) {
    const text = normalizeText(content);
    if (!text) {
      return;
    }
    const message = ensureMessageTail(items, 'assistant', 'done');
    updateThinkingStatus(message, 'end');
    replaceTextPart(message, text);
  },

  appendToolApproval(
    items: ChatTimelineItem[],
    payload: ChatTimelineToolApprovalPayload,
  ) {
    upsertTool(items, {
      approval: payload,
      input: payload.input,
      mode: 'approval',
      status: 'pending_approval',
      toolCallId: payload.toolCallId,
      toolName: payload.toolDisplayName || payload.toolName,
    });
  },

  upsertToolCall(
    items: ChatTimelineItem[],
    payload: {
      input?: unknown;
      output?: unknown;
      status?: ChatTimelineToolStatus;
      statusKey?: string;
      toolCallId?: string;
      toolName?: string;
    },
  ) {
    if (isKnowledgeRetrievalToolName(payload.toolName)) {
      ChatTimelineBuilder.upsertKnowledgeRetrievalStatus(
        items,
        payload.status === 'success' ? 'done' : 'running',
        payload.statusKey,
      );
      return;
    }
    upsertTool(items, {
      ...payload,
      mode: 'auto',
      status: payload.status || 'running',
    });
  },

  upsertKnowledgeRetrievalStatus(
    items: ChatTimelineItem[],
    status: ChatTimelineStatusStatus,
    statusKey?: string,
  ) {
    finishAssistantMessage(items, false);
    upsertStatus(items, {
      label: status === 'running' ? '正在检索知识库' : '已检索知识库',
      status,
      statusKey: knowledgeRetrievalStatusKey(statusKey),
      tone: 'muted',
    });
  },

  upsertMemoryCompressionStatus(
    items: ChatTimelineItem[],
    payload?: {
      compressed?: boolean;
      label?: string;
      phase?: string;
      status?: string;
      statusKey?: string;
    },
  ) {
    const status =
      payload?.status === 'done' || payload?.phase === 'completed'
        ? 'done'
        : 'running';
    const statusKey = payload?.statusKey || 'memory-compression';
    finishAssistantMessage(items, false);
    if (status === 'done' && payload?.compressed === false) {
      removeStatusItem(items, statusKey);
      return;
    }
    const label =
      status === 'running'
        ? payload?.label || '正在整理上下文'
        : payload?.label || '已整理上下文';
    upsertStatus(items, {
      label,
      status,
      statusKey,
      presentation: 'separator',
      tone: 'muted',
    });
  },

  markToolApproving(
    items: ChatTimelineItem[],
    payload: {
      requestId?: string;
      resumeToken?: string;
      toolCallId?: string;
    },
  ) {
    upsertTool(items, {
      ...payload,
      mode: 'approval',
      status: 'approving',
    });
  },

  markToolRejected(
    items: ChatTimelineItem[],
    payload: {
      reason?: string;
      requestId?: string;
      resumeToken?: string;
      toolCallId?: string;
    },
  ) {
    upsertTool(items, {
      ...payload,
      mode: 'approval',
      rejectReason: payload.reason,
      status: 'rejected',
    });
  },

  appendKnowledge(
    items: ChatTimelineItem[],
    knowledgeItems: ChatTimelineKnowledgeHit[],
  ) {
    if (knowledgeItems.length === 0) {
      return;
    }
    const lastAssistantMessage = [...items]
      .reverse()
      .find(
        (item): item is ChatTimelineMessageItem =>
          item.type === 'message' && item.role === 'assistant',
      );
    if (lastAssistantMessage) {
      lastAssistantMessage.knowledgeItems = [
        ...(lastAssistantMessage.knowledgeItems || []),
        ...knowledgeItems,
      ];
      return;
    }
    const last = items[items.length - 1];
    if (last?.type === 'knowledge') {
      last.items.push(...knowledgeItems);
      return;
    }
    items.push({
      id: createId('knowledge'),
      createdAt: Date.now(),
      items: knowledgeItems,
      type: 'knowledge',
    });
  },

  appendError(items: ChatTimelineItem[], message?: unknown) {
    const text = normalizeText(message) || '请求失败';
    const last = items[items.length - 1];
    if (last?.type === 'message' && last.role === 'assistant') {
      updateThinkingStatus(last, 'error');
      last.status = 'error';
    }
    items.push({
      id: createId('error'),
      createdAt: Date.now(),
      message: text,
      type: 'error',
    });
  },

  finalize(items: ChatTimelineItem[]) {
    finishRunningStatusItems(items);
    finishLastAssistantMessage(items);
  },

  replaceRoundAssistant(
    items: ChatTimelineItem[],
    roundId: string,
    message: ChatTimelineMessageItem,
  ) {
    const targetIndex = items.findIndex(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' &&
        item.role === 'assistant' &&
        item.roundId === roundId,
    );
    if (targetIndex >= 0) {
      items.splice(targetIndex, 1, message);
    }
  },
};
