import assert from "node:assert/strict";
import test from "node:test";
import { createOcrJob } from "../../lib/client/ocr/job.ts";

function setup(saved = new Map(), failPage = null) {
  const calls = [];
  const checkpoints = {
    async get(_bookId, _fingerprint, pageNumber) { return saved.get(pageNumber) ?? null; },
    async save(value) { saved.set(value.pageNumber, structuredClone(value)); },
  };
  const recognizer = {
    async recognize(_image, pageNumber) {
      calls.push(pageNumber);
      if (pageNumber === failPage) throw new Error("ocr failed");
      return {
        width: 600,
        height: 800,
        blocks: [{ text: `第 ${pageNumber} 页`, x: 10, y: 20, width: 100, height: 20, confidence: 91 }],
      };
    },
    async terminate() { calls.push("terminated"); },
  };
  const renderer = async (pageNumber) => ({ pageNumber, bitmap: {} });
  return {
    calls,
    saved,
    job: createOcrJob({
      bookId: "book-1",
      sourceFingerprint: "hash-1",
      pageCount: 3,
      checkpoints,
      renderer,
      recognizer,
      now: () => new Date("2026-08-12T02:00:00.000Z"),
    }),
  };
}

test("skips completed pages, reports progress, and checkpoints every new page", async () => {
  const existing = new Map([[1, {
    bookId: "book-1",
    sourceFingerprint: "hash-1",
    pageNumber: 1,
    width: 600,
    height: 800,
    blocks: [],
    completedAt: "2026-08-12T01:00:00.000Z",
  }]]);
  const { job, calls, saved } = setup(existing);
  const progress = [];
  const result = await job.run((event) => progress.push(event));

  assert.deepEqual(calls, [2, 3, "terminated"]);
  assert.deepEqual(result.map(({ pageNumber }) => pageNumber), [1, 2, 3]);
  assert.equal(saved.size, 3);
  assert.deepEqual(progress.at(-1), { completedPages: 3, totalPages: 3, currentPage: 3, status: "complete" });
});

test("pauses cleanly after the current page and can resume with the same checkpoints", async () => {
  const setupResult = setup();
  let paused = false;
  const first = await setupResult.job.run((event) => {
    if (!paused && event.completedPages === 1) {
      paused = true;
      setupResult.job.pause();
    }
  });
  assert.deepEqual(first.map(({ pageNumber }) => pageNumber), [1]);

  const resumed = setup(setupResult.saved);
  const final = await resumed.job.run();
  assert.deepEqual(resumed.calls, [2, 3, "terminated"]);
  assert.deepEqual(final.map(({ pageNumber }) => pageNumber), [1, 2, 3]);
});

test("terminates the recognizer and preserves earlier checkpoints when one page fails", async () => {
  const { job, calls, saved } = setup(new Map(), 2);
  await assert.rejects(job.run(), /ocr failed/);
  assert.equal(saved.has(1), true);
  assert.equal(saved.has(2), false);
  assert.equal(calls.at(-1), "terminated");
});
