import { describe, expect, it } from "vitest";

import type { AiMessageView } from "@/lib/api/types";
import {
  acceptActionFor,
  bubbleSide,
  groupByThread,
  KIND_LABEL,
  kindLabel,
  partitionByScope,
} from "./aiConversation";

// AiMessageView 工厂：只填测试关心的字段，其余给稳定默认。
function view(partial: Partial<AiMessageView>): AiMessageView {
  return {
    id: partial.id ?? crypto.randomUUID(),
    project_id: "p1",
    chapter_no: partial.chapter_no ?? null,
    thread_id: partial.thread_id ?? "t1",
    seq: partial.seq ?? 0,
    kind: partial.kind ?? "refine",
    tool_key: partial.tool_key ?? null,
    role: partial.role ?? "ai",
    content: partial.content ?? "",
    meta: partial.meta ?? {},
    created_at: partial.created_at ?? "2026-07-09T00:00:00Z",
  };
}

describe("kindLabel / KIND_LABEL", () => {
  it("五类 kind 各有中文标签", () => {
    expect(KIND_LABEL.refine).toBe("润色");
    expect(KIND_LABEL.rewrite).toBe("整章重写");
    expect(KIND_LABEL.clarify).toBe("反问澄清");
    expect(KIND_LABEL.continue).toBe("续写");
    expect(KIND_LABEL.generator).toBe("工具箱");
  });

  it("未知 kind 回退为原始串（不崩）", () => {
    expect(kindLabel("mystery")).toBe("mystery");
    expect(kindLabel("refine")).toBe("润色");
  });
});

describe("bubbleSide", () => {
  it("author 靠右、ai 靠左", () => {
    expect(bubbleSide("author")).toBe("right");
    expect(bubbleSide("ai")).toBe("left");
  });
});

describe("groupByThread", () => {
  it("按 thread_id 归组，返回每组 kind/toolKey/chapterNo", () => {
    const msgs = [
      view({ thread_id: "a", kind: "refine", chapter_no: 1, seq: 0 }),
      view({ thread_id: "b", kind: "continue", tool_key: "continue", chapter_no: 1, seq: 0 }),
      view({ thread_id: "a", kind: "refine", chapter_no: 1, seq: 1 }),
    ];
    const threads = groupByThread(msgs);
    const a = threads.find((t) => t.threadId === "a");
    expect(a?.messages).toHaveLength(2);
    expect(a?.kind).toBe("refine");
    const b = threads.find((t) => t.threadId === "b");
    expect(b?.toolKey).toBe("continue");
    expect(b?.chapterNo).toBe(1);
  });

  it("同一批次 created_at 相同时，按 seq 升序排组内（不靠 created_at 单独定序）", () => {
    // 故意乱序传入；同 created_at，靠 seq 稳定定序。
    const msgs = [
      view({ thread_id: "a", seq: 2, content: "c2", created_at: "2026-07-09T00:00:00Z" }),
      view({ thread_id: "a", seq: 0, content: "c0", created_at: "2026-07-09T00:00:00Z" }),
      view({ thread_id: "a", seq: 1, content: "c1", created_at: "2026-07-09T00:00:00Z" }),
    ];
    const thread = groupByThread(msgs)[0]!;
    expect(thread.messages.map((m) => m.content)).toEqual(["c0", "c1", "c2"]);
  });

  it("跨批次靠 created_at 定序，组内新批 append 在后", () => {
    const msgs = [
      view({ thread_id: "a", seq: 0, content: "old", created_at: "2026-07-09T00:00:00Z" }),
      view({ thread_id: "a", seq: 0, content: "new", created_at: "2026-07-09T00:05:00Z" }),
    ];
    const thread = groupByThread(msgs)[0]!;
    expect(thread.messages.map((m) => m.content)).toEqual(["old", "new"]);
  });

  it("线程按各自首条 (created_at, seq) 升序（老线程在前）", () => {
    const msgs = [
      view({ thread_id: "late", seq: 0, created_at: "2026-07-09T01:00:00Z" }),
      view({ thread_id: "early", seq: 0, created_at: "2026-07-09T00:00:00Z" }),
    ];
    const threads = groupByThread(msgs);
    expect(threads.map((t) => t.threadId)).toEqual(["early", "late"]);
  });

  it("空输入 → 空数组", () => {
    expect(groupByThread([])).toEqual([]);
  });
});

describe("partitionByScope", () => {
  it("本章线程与项目级(chapter_no=null)线程分区", () => {
    const msgs = [
      view({ thread_id: "chap", chapter_no: 3, seq: 0 }),
      view({ thread_id: "proj", chapter_no: null, kind: "generator", seq: 0 }),
    ];
    const threads = groupByThread(msgs);
    const { chapterThreads, projectThreads } = partitionByScope(threads, 3);
    expect(chapterThreads.map((t) => t.threadId)).toEqual(["chap"]);
    expect(projectThreads.map((t) => t.threadId)).toEqual(["proj"]);
  });

  it("非本章号的章线程不落入本章分区（联合查询兜底）", () => {
    const threads = groupByThread([
      view({ thread_id: "other", chapter_no: 9, seq: 0 }),
    ]);
    const { chapterThreads, projectThreads } = partitionByScope(threads, 3);
    expect(chapterThreads).toHaveLength(0);
    expect(projectThreads).toHaveLength(0);
  });
});

describe("acceptActionFor", () => {
  it("按 kind 映射接受动作；clarify/未知无动作", () => {
    const mk = (kind: string) =>
      groupByThread([view({ thread_id: kind, kind, seq: 0 })])[0]!;
    expect(acceptActionFor(mk("rewrite"))).toBe("replace_chapter");
    expect(acceptActionFor(mk("continue"))).toBe("append_draft");
    expect(acceptActionFor(mk("generator"))).toBe("append_draft");
    expect(acceptActionFor(mk("refine"))).toBe("refill_segment");
    expect(acceptActionFor(mk("clarify"))).toBeNull();
    expect(acceptActionFor(mk("mystery"))).toBeNull();
  });
});
