/**
 * Landscape promotion gate — proves the conservative auto-landscape policy
 * end-to-end through the compiled binary, asserted on pdfinfo per-page boxes
 * (the only oracle that can't lie about orientation).
 *
 * The fixture encodes one of each decision:
 *   - wide screenshot, no alt hint    → MUST stay portrait (false-positive guard)
 *   - wide image, alt "architecture diagram" → promotes
 *   - small image with {page=landscape}      → promotes (directive force)
 *   - wide mermaid sequence diagram          → promotes (provenance automatic)
 *   - wide mermaid with page=portrait fence  → MUST stay portrait (veto)
 *
 * Also runs the --toc combo: Paged.js isn't shipped in v1 (TOC renders
 * without page numbers, browse falls through after 3s), so named-page
 * landscape must survive a --toc run unchanged. If Paged.js ever lands and
 * re-paginates, this is the test that catches the interaction.
 */

import { describe, expect, test } from "bun:test";
import { execFileSync } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";

import { resolvePopplerTool } from "../../src/pdftotext";

const FIXTURE = path.resolve(__dirname, "../fixtures/landscape-gate.md");
const ROOT = path.resolve(__dirname, "../../..");
const PDF_BIN = path.join(ROOT, "make-pdf/dist/pdf");
const BROWSE_BIN = path.join(ROOT, "browse/dist/browse");
const BUNDLE = path.join(ROOT, "lib/diagram-render/dist/diagram-render.html");

const CHILD_TIMEOUT_MS = 60_000;

function prerequisitesAvailable(): { ok: true } | { ok: false; reason: string } {
  if (!fs.existsSync(PDF_BIN)) return { ok: false, reason: `make-pdf binary missing (${PDF_BIN}). Run bun run build.` };
  if (!fs.existsSync(BROWSE_BIN)) return { ok: false, reason: `browse binary missing (${BROWSE_BIN}).` };
  if (!fs.existsSync(BUNDLE)) return { ok: false, reason: `diagram-render bundle missing (${BUNDLE}).` };
  if (!fs.existsSync(FIXTURE)) return { ok: false, reason: `fixture missing (${FIXTURE}).` };
  if (!resolvePopplerTool("pdfinfo")) return { ok: false, reason: "pdfinfo not found (install poppler-utils)." };
  if (!resolvePopplerTool("pdftotext")) return { ok: false, reason: "pdftotext not found (install poppler-utils)." };
  return { ok: true };
}

interface PageBox {
  page: number;
  width: number;
  height: number;
}

function pageBoxes(pdfPath: string): PageBox[] {
  const pdfinfo = resolvePopplerTool("pdfinfo")!;
  const out = execFileSync(pdfinfo, ["-f", "1", "-l", "99", pdfPath], {
    encoding: "utf8",
    timeout: CHILD_TIMEOUT_MS,
  });
  const boxes: PageBox[] = [];
  for (const m of out.matchAll(/Page\s+(\d+)\s+size:\s+([0-9.]+)\s+x\s+([0-9.]+)\s+pts/g)) {
    boxes.push({ page: Number(m[1]), width: parseFloat(m[2]), height: parseFloat(m[3]) });
  }
  if (boxes.length === 0) throw new Error(`pdfinfo reported no page sizes:\n${out}`);
  return boxes;
}

const isLandscape = (b: PageBox) => b.width > b.height;

function generate(args: string[], outputPdf: string): void {
  execFileSync(PDF_BIN, ["generate", FIXTURE, outputPdf, "--quiet", ...args], {
    encoding: "utf8",
    env: { ...process.env, BROWSE_BIN },
    stdio: ["ignore", "pipe", "pipe"],
    timeout: CHILD_TIMEOUT_MS,
  });
}

describe("landscape promotion gate", () => {
  const avail = prerequisitesAvailable();

  test.skipIf(!avail.ok)("exactly the promoted blocks get landscape pages", () => {
    if (!avail.ok) return;
    const workDir = fs.mkdtempSync("/tmp/make-pdf-landscape-gate-");
    const outputPdf = path.join(workDir, "out.pdf");
    try {
      generate([], outputPdf);
      const boxes = pageBoxes(outputPdf);
      const landscape = boxes.filter(isLandscape);
      const portrait = boxes.filter((b) => !isLandscape(b));

      // Three promotions: alt-hinted image, directive-forced image, wide diagram.
      expect(landscape.length).toBe(3);
      // First page (intro + screenshot) and the veto'd diagram stay portrait.
      expect(portrait.length).toBeGreaterThanOrEqual(2);
      expect(isLandscape(boxes[0])).toBe(false);

      // The veto'd diagram rendered on SOME portrait page and NO landscape
      // page — the actual invariant. (Asserting a specific page index breaks
      // spuriously when font metrics shift pagination.)
      const pdftotext = resolvePopplerTool("pdftotext")!;
      const pageText = (page: number) =>
        execFileSync(pdftotext, ["-f", String(page), "-l", String(page), outputPdf, "-"], {
          encoding: "utf8",
          timeout: CHILD_TIMEOUT_MS,
        });
      expect(portrait.some((b) => pageText(b.page).includes("vetoalpha"))).toBe(true);
      expect(landscape.some((b) => pageText(b.page).includes("vetoalpha"))).toBe(false);
    } finally {
      try { fs.rmSync(workDir, { recursive: true, force: true }); } catch { /* ignore */ }
    }
  }, 120000);

  test.skipIf(!avail.ok)("--toc combo: TOC renders and landscape promotion survives", () => {
    if (!avail.ok) return;
    const workDir = fs.mkdtempSync("/tmp/make-pdf-landscape-toc-");
    const outputPdf = path.join(workDir, "out.pdf");
    try {
      generate(["--toc"], outputPdf);
      const boxes = pageBoxes(outputPdf);
      expect(boxes.filter(isLandscape).length).toBe(3);

      const pdftotext = resolvePopplerTool("pdftotext")!;
      const text = execFileSync(pdftotext, [outputPdf, "-"], { encoding: "utf8", timeout: CHILD_TIMEOUT_MS });
      // TOC heading extracts uppercase (small-caps styling).
      expect(text.toUpperCase()).toContain("CONTENTS");
    } finally {
      try { fs.rmSync(workDir, { recursive: true, force: true }); } catch { /* ignore */ }
    }
  }, 120000);

  if (!avail.ok) {
    test("landscape gate prerequisites are present (hard-required in CI)", () => {
      // Hard-require only where the binary is expected: the make-pdf gate
      // workflow is macOS-only (path-filtered) and builds dist/pdf first.
      // The Linux free lane deliberately doesn't build it — warn-skip there.
      if (process.env.CI && process.platform === 'darwin') {
        throw new Error(`landscape gate prerequisites missing in CI: ${avail.reason}`);
      }
      console.warn(`[skip] ${avail.reason}`);
    });
  }
});
