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

type ChatTimelineTurnMetadata = Partial<
  Pick<
    ChatTimelineItemBase,
    | 'roundCompleted'
    | 'roundId'
    | 'turnFinishedAt'
    | 'turnStartedAt'
    | 'turnSucceeded'
  >
>;

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 placeholder =
    role === 'assistant' && metadata?.roundId
      ? [...items]
          .reverse()
          .find(
            (item): item is ChatTimelineMessageItem =>
              item.type === 'message' &&
              item.role === 'assistant' &&
              item.roundId === metadata.roundId &&
              item.status !== 'done' &&
              item.parts.length === 0,
          )
      : undefined;
  if (placeholder) {
    placeholder.status = status;
    Object.assign(placeholder, metadata);
    return placeholder;
  }
  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 finishAssistantMessage(
  items: ChatTimelineItem[],
  roundCompleted: boolean,
  roundId?: string,
) {
  const lastMessage = [...items]
    .reverse()
    .find(
      (item): item is ChatTimelineMessageItem =>
        item.type === 'message' &&
        item.role === 'assistant' &&
        (!roundId || item.roundId === roundId),
    );
  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,
  taskId?: string,
  sourceToolCallId?: string,
  approvalId?: string,
) {
  const identities = new Set(
    [toolCallId, sourceToolCallId]
      .map((value) => normalizeText(value).trim())
      .filter(Boolean),
  );
  const normalizedTaskId = normalizeText(taskId).trim();
  const normalizedApprovalId = normalizeText(approvalId).trim();
  if (identities.size === 0 && !normalizedTaskId && !normalizedApprovalId) {
    return undefined;
  }
  return items.find(
    (item): item is ChatTimelineToolItem =>
      item.type === 'tool' &&
      ((Boolean(normalizedApprovalId) &&
        item.approval?.approvalId === normalizedApprovalId) ||
        (Boolean(normalizedTaskId) && item.taskId === normalizedTaskId) ||
        (item.toolCallId ? identities.has(item.toolCallId) : false)),
  );
}

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

function resolveStatusOccurrenceKey(
  items: ChatTimelineItem[],
  statusKey: string,
  roundId?: string,
) {
  // 同一 Turn 可多次触发整理；只复用仍在运行的实例，避免改写已完成的历史状态。
  const occurrencePrefix = `${statusKey}::occurrence:`;
  const occurrences = items.filter(
    (item): item is ChatTimelineStatusItem =>
      item.type === 'status' &&
      (item.statusKey === statusKey ||
        item.statusKey.startsWith(occurrencePrefix)) &&
      (!roundId || item.roundId === roundId),
  );
  const running = [...occurrences]
    .reverse()
    .find((item) => item.status === 'running');
  if (running) {
    return running.statusKey;
  }
  return occurrences.length === 0
    ? statusKey
    : `${occurrencePrefix}${occurrences.length + 1}`;
}

function removeStatusItem(items: ChatTimelineItem[], statusKey: string) {
  const index = items.findIndex(
    (item) => item.type === 'status' && item.statusKey === statusKey,
  );
  if (index !== -1) {
    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 skillTerminalLabel(
  label: string,
  status: Extract<ChatTimelineStatusStatus, 'cancelled' | 'incomplete'>,
) {
  const name = label.replace(/^正在调用\s*/, '').trim() || '技能';
  return status === 'cancelled' ? `已停止调用 ${name}` : `调用 ${name} 未完成`;
}

function finishRunningStatusItems(
  items: ChatTimelineItem[],
  roundId?: string,
  skillTerminalStatus: Extract<
    ChatTimelineStatusStatus,
    'cancelled' | 'incomplete'
  > = 'incomplete',
) {
  items.forEach((item) => {
    if (
      item.type !== 'status' ||
      item.status !== 'running' ||
      (roundId && item.roundId !== roundId)
    ) {
      return;
    }
    if (item.icon === 'skill') {
      item.status = skillTerminalStatus;
      item.label = skillTerminalLabel(item.label, skillTerminalStatus);
      return;
    }
    item.status = 'done';
    item.label = doneStatusLabel(item);
  });
}

function skillStatusPresentation(status: ChatTimelineSkillInvocationStatus) {
  switch (status) {
    case 'CANCELLED': {
      return {
        prefix: '已停止调用',
        status: 'cancelled' as const,
      };
    }
    case 'FAILED': {
      return { prefix: '调用', status: 'error' as const, suffix: '失败' };
    }
    case 'INCOMPLETE': {
      return {
        prefix: '调用',
        status: 'incomplete' as const,
        suffix: '未完成',
      };
    }
    case 'RUNNING': {
      return { prefix: '正在调用', status: 'running' as const };
    }
    case 'SUCCESS': {
      return { prefix: '已调用', status: 'done' as const };
    }
  }
}

function upsertStatus(
  items: ChatTimelineItem[],
  payload: ChatTimelineTurnMetadata & {
    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;
    applyTurnMetadata(found, payload);
    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',
  };
  applyTurnMetadata(item, payload);
  items.push(item);
  return item;
}

function upsertTool(
  items: ChatTimelineItem[],
  payload: ChatTimelineTurnMetadata & {
    approval?: ChatTimelineToolApprovalPayload;
    approvalId?: string;
    input?: unknown;
    mode?: ChatTimelineToolMode;
    output?: unknown;
    rejectReason?: string;
    sourceToolCallId?: string;
    status?: ChatTimelineToolStatus;
    taskId?: string;
    toolCallId?: string;
    toolName?: string;
  },
) {
  const toolCallId = normalizeText(
    payload.toolCallId ?? payload.approval?.toolCallId,
  ).trim();
  const taskId = normalizeText(payload.taskId).trim();
  const found = findToolItem(
    items,
    toolCallId,
    taskId,
    payload.sourceToolCallId,
    payload.approvalId ?? payload.approval?.approvalId,
  );
  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.taskId = taskId || found.taskId;
    found.toolCallId = toolCallId || found.toolCallId;
    found.toolName = toolName || found.toolName;
    applyTurnMetadata(found, payload);
    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'),
    taskId: taskId || undefined,
    toolCallId,
    toolName: toolName || '工具调用',
    type: 'tool',
  };
  applyTurnMetadata(toolItem, payload);
  items.push(toolItem);
  return toolItem;
}

function applyTurnMetadata(
  item: ChatTimelineItemBase,
  metadata?: ChatTimelineTurnMetadata,
) {
  if (!metadata) {
    return;
  }
  if (metadata.roundId) {
    item.roundId = metadata.roundId;
  }
  if (metadata.roundCompleted !== undefined) {
    item.roundCompleted = metadata.roundCompleted;
  }
  if (metadata.turnSucceeded !== undefined) {
    item.turnSucceeded = metadata.turnSucceeded;
  }
  if (metadata.turnStartedAt !== undefined) {
    item.turnStartedAt = Math.min(
      item.turnStartedAt ?? metadata.turnStartedAt,
      metadata.turnStartedAt,
    );
  }
  if (metadata.turnFinishedAt !== undefined) {
    item.turnFinishedAt = Math.max(
      item.turnFinishedAt ?? metadata.turnFinishedAt,
      metadata.turnFinishedAt,
    );
  }
}

export const ChatTimelineBuilder = {
  ensureAssistantTurn(
    items: ChatTimelineItem[],
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    ensureMessageTail(items, 'assistant', 'streaming', metadata);
  },

  appendUserMessage(
    items: ChatTimelineItem[],
    content?: unknown,
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    const text = normalizeText(content);
    if (!text && !metadata?.images?.length && !metadata?.documents?.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,
    metadata?: Partial<ChatTimelineMessageItem>,
  ) {
    const text = normalizeText(content);
    const message =
      (metadata?.id
        ? items.find(
            (item): item is ChatTimelineMessageItem =>
              item.type === 'message' && item.id === metadata.id,
          )
        : undefined) ||
      [...items]
        .reverse()
        .find(
          (item): item is ChatTimelineMessageItem =>
            item.type === 'message' &&
            item.role === 'assistant' &&
            (!metadata?.roundId || item.roundId === metadata.roundId),
        ) ||
      ensureMessageTail(items, 'assistant', 'done', metadata);
    for (let index = items.length - 1; index >= 0; index--) {
      const item = items[index];
      if (
        item === message ||
        item?.type !== 'message' ||
        item.role !== 'assistant' ||
        (metadata?.roundId && item.roundId !== metadata.roundId)
      ) {
        continue;
      }
      item.parts = item.parts.filter((part) => part.type !== 'text');
      if (item.parts.length === 0) {
        items.splice(index, 1);
      }
    }
    Object.assign(message, metadata);
    message.status = 'done';
    updateThinkingStatus(message, 'end');
    replaceTextPart(message, text);
  },

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

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

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

  upsertSkillInvocationStatus(
    items: ChatTimelineItem[],
    payload: ChatTimelineTurnMetadata & {
      displayName?: string;
      status: ChatTimelineSkillInvocationStatus;
      statusKey: string;
    },
  ) {
    const displayName = normalizeText(payload.displayName).trim() || '技能';
    const presentation = skillStatusPresentation(payload.status);
    const label = [presentation.prefix, displayName, presentation.suffix]
      .filter(Boolean)
      .join(' ');
    finishAssistantMessage(items, false, payload.roundId);
    upsertStatus(items, {
      ...payload,
      label,
      status: presentation.status,
      statusKey: payload.statusKey,
      tone: payload.status === 'FAILED' ? 'danger' : 'muted',
    }).icon = 'skill';
  },

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

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

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

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

  upsertArtifact(
    items: ChatTimelineItem[],
    artifact: ChatArtifactAttachment,
    metadata?: ChatTimelineTurnMetadata,
  ) {
    const artifactId = normalizeText(artifact.artifactId).trim();
    const fileName = normalizeText(artifact.fileName).trim();
    if (!artifactId || !fileName) {
      return;
    }
    const existing = items.find(
      (item): item is ChatTimelineArtifactItem =>
        item.type === 'artifact' && item.artifactId === artifactId,
    );
    if (existing) {
      existing.downloadUrl = artifact.downloadUrl;
      existing.fileName = fileName;
      existing.mimeType = artifact.mimeType;
      existing.sha256 = artifact.sha256;
      existing.size = artifact.size;
      existing.status = artifact.status;
      applyTurnMetadata(existing, metadata);
      return;
    }
    const item = {
      artifactId,
      createdAt: Date.now(),
      downloadUrl: artifact.downloadUrl,
      fileName,
      id: `artifact:${artifactId}`,
      mimeType: artifact.mimeType,
      sha256: artifact.sha256,
      size: artifact.size,
      status: artifact.status,
      type: 'artifact' as const,
    };
    applyTurnMetadata(item, metadata);
    items.push(item);
  },

  appendError(
    items: ChatTimelineItem[],
    message?: unknown,
    metadata?: ChatTimelineTurnMetadata,
  ) {
    const text = normalizeText(message) || '请求失败';
    const last = [...items]
      .reverse()
      .find(
        (item): item is ChatTimelineMessageItem =>
          item.type === 'message' &&
          item.role === 'assistant' &&
          (!metadata?.roundId || item.roundId === metadata.roundId),
      );
    if (last) {
      updateThinkingStatus(last, 'error');
      last.status = 'error';
    }
    const item = {
      id: createId('error'),
      createdAt: Date.now(),
      message: text,
      type: 'error' as const,
    };
    applyTurnMetadata(item, metadata);
    items.push(item);
  },

  finalize(
    items: ChatTimelineItem[],
    metadata?: ChatTimelineTurnMetadata,
    options?: {
      runningSkillStatus?: Extract<
        ChatTimelineStatusStatus,
        'cancelled' | 'incomplete'
      >;
    },
  ) {
    finishRunningStatusItems(
      items,
      metadata?.roundId,
      options?.runningSkillStatus,
    );
    finishAssistantMessage(
      items,
      metadata?.turnSucceeded ?? true,
      metadata?.roundId,
    );
    if (metadata?.roundId) {
      for (const item of items) {
        if (item.roundId === metadata.roundId) {
          applyTurnMetadata(item, metadata);
        }
      }
    }
  },

  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 !== -1) {
      items.splice(targetIndex, 1, message);
    }
  },
};
