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

import { emptyDecisions, setNote, setVerdict } from "@/lib/review/decisions";
import type { JobView } from "@/lib/jobs/job";
import {
  buildResumeRequest,
  CHAIN_KINDS,
  chainPhase,
  chainResultView,
} from "./chain";

function job(result: Record<string, unknown> | null): JobView {
  return {
    id: "j1",
    kind: "chain",
    status: "running",
    progress: 0,
    result,
    error: null,
  };
}

describe("chainResultView", () => {
  it("narrows a completed chain result", () => {
    const view = chainResultView(
      job({
        chain_key: "draft_volume",
        written: [1, 2, 3],
        completed: true,
        awaiting_chapter: null,
      }),
    );
    expect(view).toEqual({
      chainKey: "draft_volume",
      written: [1, 2, 3],
      awaitingChapter: null,
      completed: true,
    });
  });

  it("narrows an awaiting result (interrupt on a chapter)", () => {
    const view = chainResultView(
      job({
        chain_key: "draft_volume",
        written: [1],
        completed: false,
        awaiting_chapter: 2,
      }),
    );
    expect(view.awaitingChapter).toBe(2);
    expect(view.completed).toBe(false);
    expect(view.written).toEqual([1]);
  });

  it("returns safe defaults for null/garbage result", () => {
    expect(chainResultView(job(null))).toEqual({
      chainKey: null,
      written: [],
      awaitingChapter: null,
      completed: false,
    });
    expect(
      chainResultView(job({ written: "nope", awaiting_chapter: "x" })).written,
    ).toEqual([]);
  });

  it("keeps only finite integer chapter numbers in written", () => {
    const view = chainResultView(
      job({ written: [1, "2", 3.5, null, 4] }),
    );
    expect(view.written).toEqual([1, 4]);
  });
});

describe("chainPhase", () => {
  it("maps polling with no awaiting chapter to running", () => {
    expect(chainPhase("polling", null)).toBe("running");
  });

  it("maps any awaiting chapter to awaiting (interrupt signal is authoritative)", () => {
    // 后端 interrupt 时置 status=awaiting_input（被 narrowJob 抹平成 queued→仍 polling），
    // 但 result.awaiting_chapter 已置数；运行中该值恒 null，故它是暂停的权威信号。
    expect(chainPhase("polling", 2)).toBe("awaiting");
    expect(chainPhase("done", 2)).toBe("awaiting");
  });

  it("maps done + no awaiting to completed", () => {
    expect(chainPhase("done", null)).toBe("completed");
  });

  it("maps error to failed (even if an awaiting chapter lingers)", () => {
    expect(chainPhase("error", null)).toBe("failed");
    expect(chainPhase("error", 2)).toBe("failed");
  });
});

describe("buildResumeRequest", () => {
  it("includes only resolved decisions and trims notes (reuses accept decision logic)", () => {
    let drafts = emptyDecisions(3);
    drafts = setVerdict(drafts, 0, "accept");
    drafts = setVerdict(drafts, 2, "manual");
    drafts = setNote(drafts, 2, "  改成后天淬炼  ");
    const req = buildResumeRequest(drafts);
    expect(req.decisions).toEqual([
      { conflict_index: 0, verdict: "accept" },
      { conflict_index: 2, verdict: "manual", note: "改成后天淬炼" },
    ]);
  });

  it("empty drafts → empty decisions list", () => {
    expect(buildResumeRequest([])).toEqual({ decisions: [] });
  });
});

describe("CHAIN_KINDS", () => {
  it("exposes both built-in chain kinds aligned with backend SUPPORTED_CHAINS", () => {
    expect(CHAIN_KINDS.map((k) => k.key)).toEqual([
      "draft_volume",
      "continue_volume",
    ]);
  });

  it("gives each kind a non-empty label and description", () => {
    for (const kind of CHAIN_KINDS) {
      expect(kind.label.length).toBeGreaterThan(0);
      expect(kind.description.length).toBeGreaterThan(0);
    }
  });
});
