import assert from "node:assert/strict";
import test from "node:test";
import {
  analyzePdfDocument,
  groupPdfTextItems,
} from "../../lib/client/parsers/pdf.ts";

const item = (str, x, y, width = 100, height = 12, hasEOL = false) => ({
  str,
  transform: [1, 0, 0, 1, x, y],
  width,
  height,
  hasEOL,
});

test("groups PDF text items into lines and orders two columns", () => {
  const blocks = groupPdfTextItems([
    item("Right one.", 360, 700),
    item("Left", 50, 710, 35),
    item(" one.", 88, 710, 42, 12, true),
    item("Right two.", 360, 650),
    item("Left two.", 50, 660),
  ], { width: 700, height: 800 });

  assert.deepEqual(blocks.map(({ text }) => text), [
    "Left one.",
    "Left two.",
    "Right one.",
    "Right two.",
  ]);
  assert.equal(blocks.layout, "two-column");
});

test("classifies a readable text PDF and builds chapters from outline destinations", async () => {
  const pages = [
    { width: 600, height: 800, items: [item("第一章正文。", 50, 700, 220), item("More readable text here.", 50, 650, 220)] },
    { width: 600, height: 800, items: [item("Still chapter one.", 50, 700, 220)] },
    { width: 600, height: 800, items: [item("Chapter two starts.", 50, 700, 220)] },
  ];
  const document = {
    numPages: pages.length,
    getMetadata: async () => ({ info: { Title: "PDF Book", Author: "PDF Author" }, metadata: null }),
    getOutline: async () => [
      { title: "第一章", dest: [{ num: 1, gen: 0 }] },
      { title: "Chapter Two", dest: [{ num: 3, gen: 0 }] },
    ],
    getPageIndex: async (reference) => reference.num - 1,
    getDestination: async () => null,
    getPage: async (number) => ({
      getViewport: () => ({ width: pages[number - 1].width, height: pages[number - 1].height }),
      getTextContent: async () => ({ items: pages[number - 1].items }),
    }),
  };

  const parsed = await analyzePdfDocument(document);
  assert.equal(parsed.format, "pdf");
  assert.equal(parsed.title, "PDF Book");
  assert.equal(parsed.author, "PDF Author");
  assert.equal(parsed.requiresOcr, false);
  assert.deepEqual(parsed.chapters.map((chapter) => [chapter.title, chapter.pageStart, chapter.pageEnd]), [
    ["第一章", 1, 2],
    ["Chapter Two", 3, 3],
  ]);
  assert.equal(parsed.chapters[0].blocks.some((block) => block.text.includes("第一章正文")), true);
});

test("classifies pages with almost no text as scanned and keeps page placeholders", async () => {
  const document = {
    numPages: 3,
    getMetadata: async () => ({ info: {}, metadata: null }),
    getOutline: async () => null,
    getPage: async (number) => ({
      getViewport: () => ({ width: 600, height: 800 }),
      getTextContent: async () => ({ items: number === 2 ? [item("2", 295, 20, 10)] : [] }),
    }),
  };

  const parsed = await analyzePdfDocument(document);
  assert.equal(parsed.requiresOcr, true);
  assert.equal(parsed.pageCount, 3);
  assert.equal(parsed.chapters.length, 1);
  assert.equal(parsed.chapters[0].blocks.length, 0);
  assert.deepEqual(parsed.chapters[0].pageStart, 1);
  assert.deepEqual(parsed.chapters[0].pageEnd, 3);
});

test("rejects password-protected and malformed PDFs with clear codes", async () => {
  const loadProtected = async () => {
    const error = new Error("Password required");
    error.name = "PasswordException";
    throw error;
  };
  const loadMalformed = async () => {
    const error = new Error("Invalid PDF structure");
    error.name = "InvalidPDFException";
    throw error;
  };
  const { parsePdf } = await import("../../lib/client/parsers/pdf.ts");
  await assert.rejects(parsePdf(new Uint8Array([1]), loadProtected), (error) => error.code === "drm_or_encrypted");
  await assert.rejects(parsePdf(new Uint8Array([1]), loadMalformed), (error) => error.code === "invalid_pdf");
});
