// 内联对话（气泡流）数据装配：把已落库留痕（本章 + 指定 kinds）与「进行中」（尚未落库）的
// 实时气泡按 id 去重合并，复用 aiConversation 的 groupByThread 归组 + (created_at, seq) 定序。
// 无 React/IO，确定性纯函数，供 RefinePanel / ChapterRewritePanel 与单测共用。

import type { AiMessageView } from "@/lib/api/types";
import { groupByThread, type AiThread } from "./aiConversation";

// 进行中气泡的 created_at 恒取一个远未来常量：确保按 (created_at, seq) 定序时它们稳居
// 本章会话末尾，且不随每次渲染的「现在」漂移而无谓重排（同 localId → 无闪烁）。
export const PENDING_CREATED_AT = "9999-12-31T23:59:59.000Z";

// meta 上的私有 UI 标记键：仅内联进行中气泡设置；抽屉的已落库消息从不设置 → 其渲染完全不变。
const PENDING_FLAG = "_pending";
const STREAMING_FLAG = "_streaming";

// 一条「进行中」气泡的视图种子——面板据实时状态（缓冲的意见/反问、流式产出）合成。
export interface PendingSeed {
  // 稳定本地 id：同一轮内不变，避免流式逐 token 重渲染时气泡被当作新节点而闪烁。
  localId: string;
  role: "author" | "ai";
  content: string;
  // AI 气泡是否仍在流式/思考（据此渲染「生成中」指示，且不挂「再接受」）。
  streaming?: boolean;
}

export interface PendingContext {
  threadId: string;
  chapterNo: number;
  kind: string;
  seq: number;
}

// 合成一条未落库气泡视图（复用共享 Bubble 渲染，外观与已落库一致）。
export function toPendingView(
  seed: PendingSeed,
  ctx: PendingContext,
): AiMessageView {
  const meta: Record<string, unknown> = { [PENDING_FLAG]: true };
  if (seed.streaming) meta[STREAMING_FLAG] = true;
  return {
    id: seed.localId,
    project_id: "",
    chapter_no: ctx.chapterNo,
    thread_id: ctx.threadId,
    seq: ctx.seq,
    kind: ctx.kind,
    tool_key: null,
    role: seed.role,
    content: seed.content,
    meta,
    created_at: PENDING_CREATED_AT,
  };
}

// 是否为进行中（未落库）气泡——据此隐藏「再接受」并按进行中样式渲染。
export function isPendingView(m: AiMessageView): boolean {
  return (m.meta ?? {})[PENDING_FLAG] === true;
}

// 是否为仍在流式/思考的气泡——空正文时渲染「生成中」指示。
export function isStreamingView(m: AiMessageView): boolean {
  return (m.meta ?? {})[STREAMING_FLAG] === true;
}

// meta.segment（refine 每版都存原选段）——供从气泡「回填选段」按内容重锚。
export function readSegment(meta: AiMessageView["meta"]): string {
  const v = (meta ?? {})["segment"];
  return typeof v === "string" ? v : "";
}

// 把已落库消息收窄到本章 + 指定 kinds，与进行中气泡按 id 去重合并（已落库优先），归组成有序线程。
// fail-soft：任一入参为空都安全返回；进行中气泡若与已落库同 id（正位对账后）不重复渲染。
export function mergeInlineThreads(
  persisted: readonly AiMessageView[],
  currentChapterNo: number,
  kinds: readonly string[],
  pending: readonly AiMessageView[],
): AiThread[] {
  const kindSet = new Set(kinds);
  const scoped = persisted.filter(
    (m) => m.chapter_no === currentChapterNo && kindSet.has(m.kind),
  );
  const seen = new Set(scoped.map((m) => m.id));
  const merged = [...scoped, ...pending.filter((m) => !seen.has(m.id))];
  return groupByThread(merged);
}
