import { parseBboxLayout } from "./tesseract.provider";

/**
 * Shaped like real `pdftotext -bbox-layout` output: the gas invoice lays its
 * header out as two columns of independent text flows, so poppler puts a label
 * and the value printed beside it in *different* `<line>` elements. Trusting
 * that grouping is what left `PERIODO FACTURADO` with no value next to it and
 * every period field empty on a batch whose text was perfectly readable.
 */
function word(x: number, y: number, text: string): string {
  return `<word xMin="${x}" yMin="${y}" xMax="${x + 20}" yMax="${y + 8}">${text}</word>`;
}

function doc(...lines: string[]): string {
  return `<doc><page width="612" height="792">${lines
    .map((l) => `<flow><block><line>${l}</line></block></flow>`)
    .join("")}</page></doc>`;
}

/** Enough words on the page to clear the "is this a real text layer" floor. */
function padding(): string {
  return Array.from({ length: 50 }, (_, i) => word(10, 400 + i * 10, `w${i}`)).join("");
}

describe("parseBboxLayout", () => {
  it("rejoins a label with the value printed beside it in another flow", () => {
    const [page] = parseBboxLayout(
      doc(
        word(20, 100, "PERIODO") + word(45, 100, "FACTURADO:"),
        word(300, 100.4, "20260630-20260630"),
        padding(),
      ),
      1,
    );
    expect(page).not.toBeNull();
    expect(page!.text).toContain("PERIODO FACTURADO: 20260630-20260630");
  });

  it("keeps genuinely separate lines apart", () => {
    const [page] = parseBboxLayout(
      doc(word(20, 100, "Cuenta:") + word(80, 100, "0900003463"), word(20, 130, "Nombre:"), padding()),
      1,
    );
    expect(page!.text.split("\n")).toContain("Cuenta: 0900003463");
    expect(page!.text.split("\n")).toContain("Nombre:");
  });

  it("scales point coordinates into the render's pixel space", () => {
    // Word boxes have to land in the same coordinate space tesseract reports,
    // or the geometric helpers the parsers share silently stop finding values.
    const [page] = parseBboxLayout(doc(word(72, 144, "X") + padding()), 300 / 72);
    const x = page!.words.find((w) => w.text === "X")!;
    expect(x.left).toBeCloseTo(300);
    expect(x.top).toBeCloseTo(600);
  });

  it("reports no text layer for a scan carrying a few stray glyphs", () => {
    expect(parseBboxLayout(doc(word(10, 10, "3") + word(40, 10, "of") + word(60, 10, "5")), 1)).toEqual([
      null,
    ]);
  });

  it("decodes the entities poppler escapes", () => {
    const [page] = parseBboxLayout(doc(word(10, 10, "A&amp;B") + padding()), 1);
    expect(page!.text).toContain("A&B");
  });
});
