import fs from "fs";
import { Browser } from "puppeteer";

/**
 * Pick the first Chromium executable that actually exists on disk.
 * The previous `A || B || C` chain was dead: the second operand
 * ("/usr/bin/chromium-browser") is always truthy, so the third
 * ("/usr/bin/chromium") was never reached and launch failed on hosts
 * that only ship the `chromium` binary. We now probe each candidate
 * with fs.existsSync and fall through to the next.
 */
function resolveChromiumPath(): string | undefined {
  const candidates = [
    process.env.CHROMIUM_PATH,
    "/usr/bin/chromium-browser",
    "/usr/bin/chromium",
  ].filter((p): p is string => Boolean(p));
  for (const candidate of candidates) {
    try {
      if (fs.existsSync(candidate)) return candidate;
    } catch {
      // existsSync should not throw, but if it does treat as "not found"
      // and continue probing the next candidate.
    }
  }
  // Nothing on disk matched — fall back to the configured env value (if any)
  // so puppeteer-core surfaces a meaningful launch error instead of "undefined".
  return process.env.CHROMIUM_PATH || undefined;
}

let browser: Browser | null = null;
let launching: Promise<Browser> | null = null;

async function getBrowser(): Promise<Browser> {
  if (browser && browser.connected) return browser;

  if (launching) return launching;

  launching = (async () => {
    // Try puppeteer (bundles Chromium), fall back to puppeteer-core (system Chromium)
    try {
      const puppeteer = await import("puppeteer");
      browser = await puppeteer.default.launch({
        headless: true,
        args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
      });
    } catch {
      const core = await import("puppeteer-core");
      const executablePath = resolveChromiumPath();
      browser = await core.default.launch({
        headless: true,
        executablePath,
        args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
      });
    }
    return browser!;
  })();

  try {
    return await launching;
  } finally {
    launching = null;
  }
}

export interface HtmlToPdfOptions {
  /**
   * Chromium footerTemplate HTML rendered in the bottom margin of EVERY page
   * — the only true repeating footer Puppeteer supports (CSS @page margin
   * boxes are not implemented in Chromium). Styles must be inline and the
   * font-size explicit (Chromium defaults it to 0); `.pageNumber` /
   * `.totalPages` spans are substituted by Chromium. When set, the bottom
   * margin grows to make room and an empty headerTemplate suppresses
   * Chromium's default date/title header.
   */
  footerTemplate?: string;
  /**
   * Chromium headerTemplate HTML rendered in the top margin of EVERY page
   * (same rules as footerTemplate: inline styles, explicit font-size; images
   * must be data: URLs). When set, the top margin grows to 32mm to make room
   * — the document body must NOT render its own header then, or page 1 shows
   * it twice. NOTE: Chromium lays pages out by the document's CSS @page
   * margins when present (they override this option) — keep them in sync.
   */
  headerTemplate?: string;
}

export async function htmlToPdf(
  html: string,
  options: HtmlToPdfOptions = {},
): Promise<Buffer> {
  const b = await getBrowser();
  const page = await b.newPage();
  // Per-request timeouts so one stuck render (e.g. image tag pointing at a
  // hung host) cannot starve every subsequent PDF request. networkidle0 is
  // intentionally NOT used — it waits for 500ms of zero network connections
  // and is the original source of the indefinite-hang bug.
  try {
    await page.setContent(html, {
      waitUntil: "domcontentloaded",
      timeout: 10_000,
    });
    const pdf = await page.pdf({
      format: "A4",
      printBackground: true,
      margin: {
        top: options.headerTemplate ? "32mm" : "10mm",
        bottom: options.footerTemplate ? "18mm" : "10mm",
        left: "10mm",
        right: "10mm",
      },
      ...(options.footerTemplate || options.headerTemplate
        ? {
            displayHeaderFooter: true,
            headerTemplate: options.headerTemplate || "<span></span>",
            footerTemplate: options.footerTemplate || "<span></span>",
          }
        : {}),
      timeout: 15_000,
    });
    return Buffer.from(pdf);
  } finally {
    try {
      await page.close();
    } catch {
      // If a stuck page won't close, only close the whole browser if it
      // is actually disconnected — otherwise we just spent 1-2s relaunching
      // Chromium on every subsequent PDF request.
      if (!browser?.connected) {
        await closeBrowser();
      }
    }
  }
}

export async function closeBrowser(): Promise<void> {
  if (browser) {
    await browser.close();
    browser = null;
  }
}
