// 创作工具箱纯逻辑：声明式表单值 → 请求体组装、字段校验、output_kind → 预览渲染模型、legacy 路由解析。
// 对齐通用 toolbox 契约（GET /skills/toolbox + generate/ingest）。纯逻辑，便于 node 环境单测。
// 关键边界：UI 不硬编码每个工具的表单/预览，全部由 descriptor + 此处映射驱动（"加生成器=加声明"）。

import type {
  ToolDescriptorView,
  ToolGenerateRequest,
  ToolInputFieldView,
} from "@/lib/api/types";

// 声明式表单字段值（控件原始值；提交前经此处归一为 ToolGenerateRequest）。
export type FieldValues = Record<string, string>;

// —— 表单初值 ——
// 从 descriptor 的 input_fields 取默认值组装初始表单状态（缺省值用空串，保证受控输入）。
export function initialFieldValues(
  fields: readonly ToolInputFieldView[] | undefined,
): FieldValues {
  const values: FieldValues = {};
  for (const field of fields ?? []) {
    values[field.name] = field.default ?? "";
  }
  return values;
}

// —— 校验 ——
// 必填字段缺失 → 返回字段名列表（空数组=通过）。number 类型仅校验非空（值合法性交后端）。
export function missingRequiredFields(
  fields: readonly ToolInputFieldView[] | undefined,
  values: FieldValues,
): string[] {
  const missing: string[] = [];
  for (const field of fields ?? []) {
    if (!field.required) continue;
    if ((values[field.name] ?? "").trim().length === 0) {
      missing.push(field.label);
    }
  }
  return missing;
}

// —— 请求体组装 ——
// 已知入参字段（brief/chapter_no/count/kind）按 ToolGenerateRequest snake_case 取用；
// 其余字段名忽略（descriptor 的 input_fields 命名须落在该联合内）。number 安全解析，非法→省略。
const KNOWN_NUMBER_FIELDS = ["chapter_no", "count"] as const;

function parseOptionalNumber(raw: string | undefined): number | null {
  if (raw === undefined) return null;
  const trimmed = raw.trim();
  if (trimmed.length === 0) return null;
  const n = Number(trimmed);
  return Number.isFinite(n) ? n : null;
}

export function buildGenerateRequest(values: FieldValues): ToolGenerateRequest {
  const body: ToolGenerateRequest = { brief: (values["brief"] ?? "").trim() };
  for (const key of KNOWN_NUMBER_FIELDS) {
    const n = parseOptionalNumber(values[key]);
    if (n !== null) body[key] = n;
  }
  const kind = values["kind"]?.trim();
  if (kind) body.kind = kind;
  // 原文输入（扩写/降AI/拆书样本，text_input 策略）：非空才带（空白省略）。
  const text = values["text"]?.trim();
  if (text) body.text = text;
  return body;
}

// —— output_kind → 预览渲染模型 ——
// 把后端结构化产物（preview: 任意 schema 的 model_dump）归一为「卡片列表」渲染模型，
// 组件据此渲染（小而稳的 switch + 兜底），无需为每个工具写专属组件。
export interface PreviewField {
  label: string;
  value: string;
}

export interface PreviewItem {
  // 标题行（如脑洞 premise / 书名 title / 实体 name）；可空（纯文本变体）。
  heading: string | null;
  // 次要字段（angle/rationale/mechanism…），逐行展示。
  fields: PreviewField[];
  // 大段正文（开篇/简介变体的 text），独占一段。
  body: string | null;
}

export interface PreviewModel {
  // 用于「入库」时贴对应 schema（world_entities / outline scenes）。
  kind: string;
  items: PreviewItem[];
}

function asString(v: unknown): string {
  if (typeof v === "string") return v;
  if (typeof v === "number" || typeof v === "boolean") return String(v);
  return "";
}

function asStringArray(v: unknown): string[] {
  if (!Array.isArray(v)) return [];
  return v.filter((x): x is string => typeof x === "string");
}

function asRecordArray(v: unknown): Record<string, unknown>[] {
  if (!Array.isArray(v)) return [];
  return v.filter(
    (x): x is Record<string, unknown> => typeof x === "object" && x !== null,
  );
}

// 取 preview 对象里第一个数组型字段（各工具的列表键名不同：ideas/titles/variants/names/...）。
function firstArrayEntry(
  preview: Record<string, unknown> | undefined,
): unknown[] {
  if (!preview) return [];
  for (const value of Object.values(preview)) {
    if (Array.isArray(value)) return value;
  }
  return [];
}

// 把一条记录的若干「次要字段」按 (key,label) 映射收集为 PreviewField（缺省字段跳过）。
function collectFields(
  row: Record<string, unknown>,
  specs: readonly { key: string; label: string }[],
): PreviewField[] {
  const fields: PreviewField[] = [];
  for (const spec of specs) {
    const value = asString(row[spec.key]);
    if (value) fields.push({ label: spec.label, value });
  }
  return fields;
}

function rulesToField(row: Record<string, unknown>): PreviewField[] {
  const rules = asStringArray(row["rules"]);
  return rules.length > 0
    ? [{ label: "硬规则", value: rules.join("；") }]
    : [];
}

// 取 preview 里的原始记录数组（供入库形变；与 mapPreview 同源，保证选中下标对齐）。
export function previewRows(
  preview: Record<string, unknown> | undefined,
): Record<string, unknown>[] {
  return asRecordArray(firstArrayEntry(preview));
}

// 单对象产物（非列表）的 output_kind：续写/扩写/降AI（纯正文 {text}）+ 拆书（结构化对象）。
// 这些 preview 本身就是一个对象（无顶层数组），故不走 firstArrayEntry 的「记录列表」假设，
// 而归一为「单张卡」（Scope B 竞品快赢生成器）。
const SINGLE_OBJECT_KINDS = new Set([
  "ContinuationResult",
  "PolishResult",
  "DeAiResult",
  "BookTeardownResult",
]);

// output_kind → 行渲染策略。未知 kind 走兜底（heading=首个字符串字段，其余键值列出）。
export function mapPreview(
  outputKind: string,
  preview: Record<string, unknown> | undefined,
): PreviewModel {
  if (SINGLE_OBJECT_KINDS.has(outputKind)) {
    const item = singleObjectItem(outputKind, preview);
    return { kind: outputKind, items: item ? [item] : [] };
  }
  const rows = asRecordArray(firstArrayEntry(preview));
  const items = rows.map((row) => previewItem(outputKind, row));
  return { kind: outputKind, items };
}

// 把单对象产物归一为一张预览卡：纯正文类 → body；拆书 → structure 作 heading + 列表字段。
// preview 缺失/正文为空 → 返 null（mapPreview 据此给空 items，不渲空卡）。
function singleObjectItem(
  outputKind: string,
  preview: Record<string, unknown> | undefined,
): PreviewItem | null {
  if (!preview) return null;
  if (outputKind === "BookTeardownResult") {
    return teardownItem(preview);
  }
  const text = asString(preview["text"]);
  return text ? { heading: null, fields: [], body: text } : null;
}

// 拆书结构化产物 → 一张卡：structure 作标题，themes/archetypes/hooks 各拼成一行字段。
function teardownItem(preview: Record<string, unknown>): PreviewItem {
  const listField = (key: string, label: string): PreviewField[] => {
    const joined = asStringArray(preview[key]).join("；");
    return joined ? [{ label, value: joined }] : [];
  };
  return {
    heading: asString(preview["structure"]) || null,
    fields: [
      ...listField("themes", "主题"),
      ...listField("archetypes", "人物原型"),
      ...listField("hooks", "钩子套路"),
    ],
    body: null,
  };
}

function previewItem(
  outputKind: string,
  row: Record<string, unknown>,
): PreviewItem {
  switch (outputKind) {
    case "IdeaListResult":
      return {
        heading: asString(row["premise"]) || null,
        fields: collectFields(row, [
          { key: "hook", label: "爽点" },
          { key: "genre_fit", label: "题材契合" },
        ]),
        body: null,
      };
    case "TitleListResult":
      return {
        heading: asString(row["title"]) || null,
        fields: collectFields(row, [{ key: "rationale", label: "立意" }]),
        body: null,
      };
    case "BlurbResult":
      return {
        heading: asString(row["angle"]) || null,
        fields: [],
        body: asString(row["text"]) || null,
      };
    case "NameListResult":
      return {
        heading: asString(row["name"]) || null,
        fields: collectFields(row, [
          { key: "kind", label: "类别" },
          { key: "note", label: "说明" },
        ]),
        body: null,
      };
    case "GoldenFingerResult":
      return {
        heading: asString(row["name"]) || null,
        fields: collectFields(row, [
          { key: "mechanism", label: "机制" },
          { key: "growth", label: "成长" },
          { key: "limits", label: "限制" },
        ]),
        body: null,
      };
    case "GlossaryResult":
      return {
        heading: asString(row["name"]) || null,
        fields: [
          ...collectFields(row, [
            { key: "type", label: "类型" },
            { key: "definition", label: "释义" },
          ]),
          ...rulesToField(row),
        ],
        body: null,
      };
    case "OpeningResult":
      return { heading: null, fields: [], body: asString(row["text"]) || null };
    case "DetailedOutlineResult":
      return {
        heading: asString(row["beat"]) || null,
        fields: collectFields(row, [
          { key: "idx", label: "序" },
          { key: "purpose", label: "作用" },
          { key: "conflict", label: "冲突" },
          { key: "hook", label: "钩子" },
        ]),
        body: null,
      };
    default:
      return fallbackItem(row);
  }
}

// 兜底：首个字符串字段当标题，其余键值平铺（未知 output_kind 也能展示，不报错）。
function fallbackItem(row: Record<string, unknown>): PreviewItem {
  const entries = Object.entries(row);
  let heading: string | null = null;
  const fields: PreviewField[] = [];
  for (const [key, raw] of entries) {
    const value = Array.isArray(raw) ? asStringArray(raw).join("；") : asString(raw);
    if (!value) continue;
    if (heading === null) {
      heading = value;
    } else {
      fields.push({ label: key, value });
    }
  }
  return { heading, fields, body: null };
}

// —— 模板填入目标 ——
// 模板 body 一键填入的目标输入字段：优先 brief（多数生成器主输入），否则 text（原文输入类）。
// 都没有则 null（不渲染「从模板填入」）。F3：纯前端，不改生成器后端。
const TEMPLATE_FILL_FIELDS = ["brief", "text"] as const;

export function templateFillTarget(
  fields: readonly ToolInputFieldView[] | undefined,
): string | null {
  const names = new Set((fields ?? []).map((f) => f.name));
  for (const candidate of TEMPLATE_FILL_FIELDS) {
    if (names.has(candidate)) return candidate;
  }
  return null;
}

// —— legacy 路由解析 ——
// legacy 工具的 legacy_route 含 {id} 占位（如 /projects/{id}/codex?gen=world）→ 替换为真实 projectId。
export function resolveLegacyRoute(
  route: string,
  projectId: string,
): string {
  return route.replace(/\{id\}/g, projectId);
}

// 判定工具点击应导航（legacy）还是开 runner（新工具）。
export function isLegacyTool(tool: Readonly<ToolDescriptorView>): boolean {
  return tool.is_legacy && !!tool.legacy_route;
}

// —— 「关联工具」下拉选项 ——
// 工具描述符 → 下拉选项（value=key 用于 tool_key 关联，label=中文标题供展示，不暴露英文 key）。
// 模板库「关联生成器」字段据此从纯文本框升级为 Select。
export interface ToolKeyOption {
  key: string;
  title: string;
}

export function toolKeyOptions(
  tools: readonly ToolDescriptorView[] | undefined,
): ToolKeyOption[] {
  return (tools ?? []).map((tool) => ({ key: tool.key, title: tool.title }));
}
