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.
 */
/**
 * Boxes are sized from the text, at 6 units a character: the reassembler now
 * reads the space BETWEEN two boxes, so a fixed width would put a fabricated
 * gap after every short word and every row would come back column-padded.
 */
function word(x: number, y: number, text: string): string {
  return `<word xMin="${x}" yMin="${y}" xMax="${x + text.length * 6}" 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(68, 100, "FACTURADO:"),
        word(300, 100.4, "20260630-20260630"),
        padding(),
      ),
      1,
    );
    expect(page).not.toBeNull();
    expect(page!.text).toMatch(/PERIODO FACTURADO:\s+20260630-20260630/);
  });

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

  /**
   * The layout is data. A borderless table separates its cells with nothing
   * but white space, so the parsers read a run of spaces as a cell boundary
   * (`INSURED\s{2,}`) and a column offset as a column (`SUM INSURED` vs
   * `PREMIUM`). Both regressed to nothing when this collapsed every gap to a
   * single space, and the fixtures — taken from `pdftotext -layout`, which
   * prints the gaps — could not see it.
   */
  it("preserves the gap between two cells of a borderless table", () => {
    const [page] = parseBboxLayout(
      doc(word(20, 100, "INSURED") + word(300, 100, "PAMELA") + word(340, 100, "WAGONER"), padding()),
      1,
    );
    const line = page!.text.split("\n").find((l) => l.includes("INSURED"))!;
    expect(line).toMatch(/INSURED\s{2,}PAMELA WAGONER/);
  });

  it("preserves the blank line between two blocks", () => {
    const [page] = parseBboxLayout(
      doc(word(20, 100, "Insured"), word(20, 112, "wraps"), word(20, 200, "Next"), padding()),
      1,
    );
    const lines = page!.text.split("\n").map((l) => l.trim());
    // The wrapped continuation stays attached; the next block is cut off from
    // it, which is what stops a "join until the cell ends" walk running away.
    expect(lines.slice(lines.indexOf("Insured"), lines.indexOf("Next") + 1)).toEqual([
      "Insured",
      "wraps",
      "",
      "Next",
    ]);
  });

  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");
  });
});
