// 多章工作流链前端纯逻辑（Scope B）：把 `GET /jobs/{id}` 的链 job result 安全收窄成
// 视图模型；把轮询状态 + interrupt 标志映射成链相位；裁决草稿 → resume 请求体。
// 纯逻辑（无 React / 无 fetch），便于 node 环境单测。链走 job 轮询（非 SSE）。

import type { ChainResumeRequest } from "@/lib/api/types";
import type { JobView, PollStatus } from "@/lib/jobs/job";
import { buildAcceptRequest, type DecisionDraft } from "@/lib/review/decisions";

// 内置链类型（对齐后端 SUPPORTED_CHAINS）：draft_volume=从 start 章按记忆量产；
// continue_volume=续写式（每章以上一章 accepted 正文末尾作前文引子，比纯 digest 更顺）。
export type ChainKind = "draft_volume" | "continue_volume";

export interface ChainKindOption {
  key: ChainKind;
  label: string;
  description: string;
}

export const CHAIN_KINDS: readonly ChainKindOption[] = [
  {
    key: "draft_volume",
    label: "从头写",
    description: "按记忆与大纲量产，每章独立写。",
  },
  {
    key: "continue_volume",
    label: "续写",
    description: "每章以上一章已验收正文末尾作前文引子，承接更顺。",
  },
];

// 链 job result 的视图模型（与后端 `_chain_result` 字段对齐：chain_key/written/completed/
// awaiting_chapter）。token/正文绝不入 result（不变量 #5 / chain §5），故此处只有进度元数据。
export interface ChainResultView {
  chainKey: string | null;
  // 已写完（已验收/已晋升）的章号清单。
  written: number[];
  // interrupt 命中、等作者裁决的章号；null = 未暂停（跑完或运行中）。
  awaitingChapter: number | null;
  // 全链跑完（无 interrupt 残留）。
  completed: boolean;
}

function asIntList(v: unknown): number[] {
  if (!Array.isArray(v)) return [];
  return v.filter(
    (n): n is number => typeof n === "number" && Number.isInteger(n),
  );
}

function asIntOrNull(v: unknown): number | null {
  return typeof v === "number" && Number.isInteger(v) ? v : null;
}

// 把链 job 的 result 收窄成 ChainResultView（缺字段/脏数据给安全默认，守边界 #not-trust）。
export function chainResultView(job: JobView): ChainResultView {
  const r = job.result ?? {};
  return {
    chainKey: typeof r["chain_key"] === "string" ? r["chain_key"] : null,
    written: asIntList(r["written"]),
    awaitingChapter: asIntOrNull(r["awaiting_chapter"]),
    completed: r["completed"] === true,
  };
}

// 链相位（驱动页面 UI 分支）：运行中 / 待裁决 / 跑完 / 失败。
export type ChainPhase = "running" | "awaiting" | "completed" | "failed";

// 后端 interrupt 时把 job 置 awaiting_input（非 done/failed）+ result.awaiting_chapter=N——
// `narrowJob` 把未知态 awaiting_input 归到 "queued"，故轮询 reducer 仍 polling；运行中
// awaiting_chapter 恒为 null，仅 interrupt 命中才置数，故以 `awaitingChapter !== null` 为
// 暂停的权威信号（不依赖被抹平的原始状态），让页面在暂停时进入裁决相而非空轮询（不变量 #5）。
export function chainPhase(
  pollStatus: PollStatus,
  awaitingChapter: number | null,
): ChainPhase {
  if (pollStatus === "error") return "failed";
  if (awaitingChapter !== null) return "awaiting";
  if (pollStatus === "done") return "completed";
  return "running";
}

// 裁决草稿 → resume 请求体。复用 accept 的裁决组装逻辑（DRY；resume.decisions 与
// accept.decisions 同为 ConflictDecision[]），仅丢弃 final_text（链续跑不带终稿）。
export function buildResumeRequest(
  drafts: readonly DecisionDraft[],
): ChainResumeRequest {
  const { decisions } = buildAcceptRequest("", drafts);
  return { decisions };
}
