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

import type { ToolDescriptorView, ToolInputFieldView } from "@/lib/api/types";
import {
  buildGenerateRequest,
  initialFieldValues,
  isLegacyTool,
  mapPreview,
  missingRequiredFields,
  previewRows,
  resolveLegacyRoute,
  templateFillTarget,
  toolKeyOptions,
} from "./toolbox";

const field = (over: Partial<ToolInputFieldView> = {}): ToolInputFieldView => ({
  name: "brief",
  label: "需求",
  type: "textarea",
  required: true,
  ...over,
});

describe("initialFieldValues", () => {
  it("seeds defaults and empty strings", () => {
    const values = initialFieldValues([
      field({ name: "brief", default: undefined }),
      field({ name: "count", type: "number", default: "3", required: false }),
    ]);
    expect(values).toEqual({ brief: "", count: "3" });
  });

  it("handles undefined fields", () => {
    expect(initialFieldValues(undefined)).toEqual({});
  });
});

describe("missingRequiredFields", () => {
  it("reports labels of blank required fields only", () => {
    const fields = [
      field({ name: "brief", label: "需求", required: true }),
      field({ name: "count", label: "数量", required: false }),
    ];
    expect(missingRequiredFields(fields, { brief: "  ", count: "" })).toEqual([
      "需求",
    ]);
  });

  it("passes when all required present", () => {
    expect(
      missingRequiredFields([field()], { brief: "一个脑洞" }),
    ).toEqual([]);
  });
});

describe("buildGenerateRequest", () => {
  it("trims brief and maps known number fields", () => {
    expect(
      buildGenerateRequest({ brief: "  修真  ", count: "5", chapter_no: "3" }),
    ).toEqual({ brief: "修真", count: 5, chapter_no: 3 });
  });

  it("omits blank/invalid numbers and empty kind", () => {
    expect(
      buildGenerateRequest({ brief: "x", count: "", chapter_no: "abc", kind: " " }),
    ).toEqual({ brief: "x" });
  });

  it("includes kind when present", () => {
    expect(buildGenerateRequest({ brief: "x", kind: "门派" })).toEqual({
      brief: "x",
      kind: "门派",
    });
  });

  it("defaults brief to empty string", () => {
    expect(buildGenerateRequest({})).toEqual({ brief: "" });
  });

  it("forwards trimmed text (扩写/降AI/拆书 原文输入)", () => {
    expect(
      buildGenerateRequest({ brief: "扩写", text: "  原始正文片段  " }),
    ).toEqual({ brief: "扩写", text: "原始正文片段" });
  });

  it("omits blank/whitespace-only text", () => {
    expect(buildGenerateRequest({ brief: "x", text: "   " })).toEqual({
      brief: "x",
    });
  });
});

describe("previewRows", () => {
  it("picks the first array field as records", () => {
    const rows = previewRows({ ideas: [{ premise: "a" }, { premise: "b" }] });
    expect(rows).toHaveLength(2);
    expect(rows[0]).toEqual({ premise: "a" });
  });

  it("returns empty for missing/non-array", () => {
    expect(previewRows(undefined)).toEqual([]);
    expect(previewRows({ note: "x" })).toEqual([]);
  });
});

describe("mapPreview", () => {
  it("maps IdeaListResult to heading + fields", () => {
    const model = mapPreview("IdeaListResult", {
      ideas: [{ premise: "废柴逆袭", hook: "系统觉醒", genre_fit: "玄幻" }],
    });
    expect(model.kind).toBe("IdeaListResult");
    expect(model.items[0]?.heading).toBe("废柴逆袭");
    expect(model.items[0]?.fields).toEqual([
      { label: "爽点", value: "系统觉醒" },
      { label: "题材契合", value: "玄幻" },
    ]);
  });

  it("maps BlurbResult text into body and angle into heading", () => {
    const model = mapPreview("BlurbResult", {
      variants: [{ angle: "悬念向", text: "一段简介" }],
    });
    expect(model.items[0]?.heading).toBe("悬念向");
    expect(model.items[0]?.body).toBe("一段简介");
  });

  it("maps GlossaryResult rules into a joined field", () => {
    const model = mapPreview("GlossaryResult", {
      terms: [{ name: "灵气", type: "概念", rules: ["不可逆", "有上限"] }],
    });
    const ruleField = model.items[0]?.fields.find((f) => f.label === "硬规则");
    expect(ruleField?.value).toBe("不可逆；有上限");
  });

  it("maps OpeningResult as pure body", () => {
    const model = mapPreview("OpeningResult", {
      variants: [{ text: "开篇第一段" }],
    });
    expect(model.items[0]?.heading).toBeNull();
    expect(model.items[0]?.body).toBe("开篇第一段");
  });

  it("falls back gracefully for unknown output_kind", () => {
    const model = mapPreview("MysteryResult", {
      things: [{ a: "first", b: "second" }],
    });
    expect(model.items[0]?.heading).toBe("first");
    expect(model.items[0]?.fields).toEqual([{ label: "b", value: "second" }]);
  });

  it("yields no items for empty preview", () => {
    expect(mapPreview("IdeaListResult", undefined).items).toEqual([]);
  });

  it("maps a text-only result (续写/扩写/降AI) into a single body item", () => {
    for (const kind of ["ContinuationResult", "PolishResult", "DeAiResult"]) {
      const model = mapPreview(kind, { text: "一段续写/改写后的正文" });
      expect(model.items).toHaveLength(1);
      expect(model.items[0]?.heading).toBeNull();
      expect(model.items[0]?.body).toBe("一段续写/改写后的正文");
    }
  });

  it("maps BookTeardownResult structured fields into one card", () => {
    const model = mapPreview("BookTeardownResult", {
      themes: ["逆袭", "复仇"],
      archetypes: ["废柴主角"],
      structure: "三幕递进",
      hooks: ["开局打脸", "扮猪吃虎"],
    });
    expect(model.items).toHaveLength(1);
    const item = model.items[0];
    expect(item?.heading).toBe("三幕递进");
    const byLabel = (label: string) =>
      item?.fields.find((f) => f.label === label)?.value;
    expect(byLabel("主题")).toBe("逆袭；复仇");
    expect(byLabel("人物原型")).toBe("废柴主角");
    expect(byLabel("钩子套路")).toBe("开局打脸；扮猪吃虎");
  });

  it("yields no items for an empty text result", () => {
    expect(mapPreview("ContinuationResult", { text: "" }).items).toEqual([]);
    expect(mapPreview("ContinuationResult", undefined).items).toEqual([]);
  });
});

describe("resolveLegacyRoute", () => {
  it("substitutes the {id} placeholder", () => {
    expect(resolveLegacyRoute("/projects/{id}/codex?gen=world", "p9")).toBe(
      "/projects/p9/codex?gen=world",
    );
  });
});

describe("isLegacyTool", () => {
  const tool = (over: Partial<ToolDescriptorView>): ToolDescriptorView => ({
    key: "k",
    title: "t",
    subtitle: "s",
    is_legacy: false,
    ingestable: false,
    ...over,
  });

  it("is true only with legacy flag and a route", () => {
    expect(
      isLegacyTool(tool({ is_legacy: true, legacy_route: "/x/{id}" })),
    ).toBe(true);
    expect(isLegacyTool(tool({ is_legacy: true }))).toBe(false);
    expect(
      isLegacyTool(tool({ is_legacy: false, legacy_route: "/x/{id}" })),
    ).toBe(false);
  });

  it("maps descriptors to key/title options", () => {
    expect(
      toolKeyOptions([
        tool({ key: "idea", title: "脑洞生成器" }),
        tool({ key: "title", title: "书名生成器" }),
      ]),
    ).toEqual([
      { key: "idea", title: "脑洞生成器" },
      { key: "title", title: "书名生成器" },
    ]);
  });

  it("returns empty array for undefined tools", () => {
    expect(toolKeyOptions(undefined)).toEqual([]);
  });
});

describe("templateFillTarget", () => {
  it("prefers brief when present", () => {
    expect(
      templateFillTarget([field({ name: "brief" }), field({ name: "text" })]),
    ).toBe("brief");
  });

  it("falls back to text when brief absent", () => {
    expect(templateFillTarget([field({ name: "text" })])).toBe("text");
  });

  it("returns null when no fillable field exists", () => {
    expect(templateFillTarget([field({ name: "count" })])).toBeNull();
    expect(templateFillTarget(undefined)).toBeNull();
  });
});
