#!/usr/bin/env node
// build-pdf.mjs — render kronikkene som rene avis-PDF-er (uten annoterings-UI).
// Bruk: node build-pdf.mjs utkast/01-....md [flere.md ...]
// Genererer ren print-HTML i pdf/_html/<navn>.html og kjører weasyprint -> pdf/<navn>.pdf.
// Speiler avis-stilen fra build-html.mjs, men print-tunet (A4, marger, sidetall).
// Krever: weasyprint på PATH. Ingen npm-avhengigheter.

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { execFileSync } from "node:child_process";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// ---------------------------------------------------------------------------
// weasyprint graceful degradation (S1, correction #3)
// Detekterer weasyprint på PATH. Returnerer et skip-signal (kaster ALDRI) når
// verktøyet mangler, slik at PDF-steget hoppes over med en tydelig install-hint
// i stedet for å krasje kjøringen. `probe` er injiserbar for test.
// ---------------------------------------------------------------------------
const WEASYPRINT_HINT =
  "weasyprint ikke funnet på PATH — hopper over PDF-steget.\n" +
  "  Install: pipx install weasyprint   (alternativt: brew install weasyprint)";

export function resolveWeasyprint(probe = defaultWeasyprintProbe) {
  if (probe()) return { available: true };
  return { available: false, hint: WEASYPRINT_HINT };
}

function defaultWeasyprintProbe() {
  try {
    execFileSync("weasyprint", ["--version"], { stdio: "ignore" });
    return true;
  } catch {
    return false;
  }
}

// ---------------------------------------------------------------------------
// YAML front matter (flate key: "value"-par mellom --- ... ---) — som build-html.mjs
// ---------------------------------------------------------------------------
function parseFrontMatter(raw) {
  const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
  if (!m) return { meta: {}, body: raw };
  const meta = {};
  for (const line of m[1].split(/\r?\n/)) {
    const mm = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
    if (!mm) continue;
    let val = mm[2].trim();
    if (
      (val.startsWith('"') && val.endsWith('"')) ||
      (val.startsWith("'") && val.endsWith("'"))
    ) {
      val = val.slice(1, -1);
    }
    meta[mm[1]] = val;
  }
  return { meta, body: m[2].replace(/^\r?\n+/, "") };
}

function esc(s) {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function inline(text) {
  let out = esc(text);
  out = out.replace(/\*\*([^*]+)\*\*/g, (_, c) => `<strong>${c}</strong>`);
  out = out.replace(/\*([^*]+)\*/g, (_, c) => `<em>${c}</em>`);
  return out;
}

// ---------------------------------------------------------------------------
// Kompakt markdown -> HTML (som build-html.mjs). Siste avsnitt-blokk som starter
// med <em>Om tilblivelsen:</em> merkes .colophon for diskret metodenote-stil.
// ---------------------------------------------------------------------------
function markdownToHtml(body) {
  const lines = body.replace(/\r\n/g, "\n").split("\n");
  const blocks = [];
  let i = 0;
  let paraCount = 0;

  function flushPara(buf) {
    if (!buf.length) return;
    const text = buf.join(" ").trim();
    if (!text) return;
    paraCount++;
    let cls = paraCount === 1 ? "lede" : "indent";
    if (/^\*Om tilblivelsen:\*/.test(text)) cls = "colophon";
    let inner = inline(text);
    // Drop cap som ekte, floatet <span> (weasyprint krasjer på ::first-letter{float}).
    if (cls === "lede") {
      inner = inner.replace(
        /^(\s*)([A-Za-zÆØÅæøå0-9])/,
        (_, ws, ch) => `${ws}<span class="dropcap">${ch}</span>`
      );
    }
    blocks.push(`<p class="${cls}">${inner}</p>`);
  }

  while (i < lines.length) {
    const line = lines[i];
    const trimmed = line.trim();

    if (/^---+$/.test(trimmed)) {
      blocks.push("<hr>");
      i++;
      continue;
    }

    let hm = trimmed.match(/^(#{2,3})\s+(.*)$/);
    if (hm) {
      const level = hm[1].length;
      blocks.push(`<h${level}>${inline(hm[2].trim())}</h${level}>`);
      i++;
      continue;
    }

    if (/^>\s?/.test(trimmed)) {
      const qbuf = [];
      while (i < lines.length && /^>\s?/.test(lines[i].trim())) {
        qbuf.push(lines[i].trim().replace(/^>\s?/, ""));
        i++;
      }
      blocks.push(`<blockquote><p>${inline(qbuf.join(" ").trim())}</p></blockquote>`);
      continue;
    }

    if (/^[-*]\s+/.test(trimmed)) {
      const items = [];
      while (i < lines.length && /^[-*]\s+/.test(lines[i].trim())) {
        items.push(lines[i].trim().replace(/^[-*]\s+/, ""));
        i++;
      }
      blocks.push("<ul>" + items.map((it) => `<li>${inline(it)}</li>`).join("") + "</ul>");
      continue;
    }

    if (/^\d+\.\s+/.test(trimmed)) {
      const items = [];
      while (i < lines.length && /^\d+\.\s+/.test(lines[i].trim())) {
        items.push(lines[i].trim().replace(/^\d+\.\s+/, ""));
        i++;
      }
      blocks.push("<ol>" + items.map((it) => `<li>${inline(it)}</li>`).join("") + "</ol>");
      continue;
    }

    if (trimmed === "") {
      i++;
      continue;
    }

    const pbuf = [];
    while (i < lines.length) {
      const t = lines[i].trim();
      if (
        t === "" ||
        /^---+$/.test(t) ||
        /^(#{2,3})\s+/.test(t) ||
        /^>\s?/.test(t) ||
        /^[-*]\s+/.test(t) ||
        /^\d+\.\s+/.test(t)
      ) {
        break;
      }
      pbuf.push(t);
      i++;
    }
    flushPara(pbuf);
  }

  return blocks.join("\n");
}

// ---------------------------------------------------------------------------
// Print-CSS — avis-identitet (off-white, oxblood, drop cap, serif), A4-tunet.
// ---------------------------------------------------------------------------
const FONT_DIR = path.join(__dirname, "fonts");
const ff = (f) => `url("file://${path.join(FONT_DIR, f).replace(/ /g, "%20")}")`;
const FONT_FACE = `
@font-face{font-family:"Newsreader";font-style:normal;font-weight:400;src:${ff("Newsreader-400.ttf")};}
@font-face{font-family:"Newsreader";font-style:italic;font-weight:400;src:${ff("Newsreader-400i.ttf")};}
@font-face{font-family:"Newsreader";font-style:normal;font-weight:600;src:${ff("Newsreader-600.ttf")};}
@font-face{font-family:"Newsreader";font-style:italic;font-weight:600;src:${ff("Newsreader-600i.ttf")};}
@font-face{font-family:"Newsreader";font-style:normal;font-weight:700;src:${ff("Newsreader-700.ttf")};}
@font-face{font-family:"Inter";font-style:normal;font-weight:400;src:${ff("Inter-400.ttf")};}
@font-face{font-family:"Inter";font-style:normal;font-weight:600;src:${ff("Inter-600.ttf")};}
@font-face{font-family:"Inter";font-style:normal;font-weight:700;src:${ff("Inter-700.ttf")};}
`;
const PRINT_CSS = `
${FONT_FACE}
:root {
  --bg: #FBFAF7;
  --ink: #1A1A1A;
  --muted: #555555;
  --accent: #9A3324;
  --rule: #d8d4cb;
  --serif: "Newsreader",Georgia,"Times New Roman",serif;
  --sans: "Inter","Helvetica Neue",Helvetica,Arial,sans-serif;
}
@page {
  size: A4;
  margin: 22mm 21mm 20mm;
  background: #FBFAF7;
  @bottom-center {
    content: counter(page);
    font-family: "Inter","Helvetica Neue",Helvetica,Arial,sans-serif;
    font-size: 8pt;
    color: #9a9a9a;
  }
}
* { box-sizing: border-box; }
html { background: var(--bg); }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 12.5pt;
  line-height: 1.5;
}
.kicker {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 8pt;
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 8pt;
}
h1.title {
  font-family: var(--serif);
  font-size: 27pt;
  line-height: 1.08;
  font-weight: 700;
  margin: 0 0 9pt;
  letter-spacing: -0.01em;
}
.subtitle {
  font-family: var(--serif);
  font-style: italic;
  font-size: 14.5pt;
  line-height: 1.4;
  color: var(--muted);
  margin: 0 0 14pt;
}
.byline-wrap {
  border-top: 1px solid var(--rule);
  padding-top: 7pt;
  margin-bottom: 18pt;
}
.byline {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 8pt;
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 2pt;
}
.meta {
  font-family: var(--sans);
  font-size: 8pt;
  color: var(--muted);
  letter-spacing: 0.04em;
}
.body p { margin: 0; text-align: justify; hyphens: auto; orphans: 3; widows: 3; }
.body p.indent { text-indent: 1.4em; }
.body .dropcap {
  float: left;
  font-size: 3.1em;
  line-height: 0.74;
  padding: 0.02em 0.09em 0 0;
  color: var(--accent);
  font-weight: 700;
}
.body h2 { font-size: 16.5pt; font-weight: 700; margin: 18pt 0 6pt; line-height: 1.2; }
.body h3 { font-size: 13.5pt; font-weight: 700; margin: 15pt 0 4pt; line-height: 1.25; }
.body ul, .body ol { margin: 7pt 0; padding-left: 1.4em; }
.body li { margin: 2pt 0; }
.body blockquote {
  margin: 12pt 0;
  padding-left: 1.1em;
  border-left: 3px solid var(--accent);
  font-style: italic;
  color: #333;
}
.body hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 16pt auto;
  width: 38%;
}
.body strong { font-weight: 600; }
.body p.colophon {
  font-family: var(--sans);
  font-size: 9.5pt;
  line-height: 1.45;
  color: var(--muted);
  text-align: left;
  text-indent: 0;
}
.body h2, .body h3 { break-after: avoid; }
.body p.lede { break-after: avoid; }
`;

function buildPrintHtml(meta, body) {
  const bodyHtml = markdownToHtml(body);
  const title = meta.title || "Kronikk";
  const metaLine = [meta.serie, meta.lesetid].filter(Boolean).join(" · ");
  return `<!DOCTYPE html>
<html lang="nb">
<head>
<meta charset="utf-8">
<title>${esc(title)}</title>
<style>${PRINT_CSS}</style>
</head>
<body>
<article>
  ${meta.kicker ? `<p class="kicker">${esc(meta.kicker)}</p>` : ""}
  <h1 class="title">${inline(title)}</h1>
  ${meta.subtitle ? `<p class="subtitle">${inline(meta.subtitle)}</p>` : ""}
  <div class="byline-wrap">
    ${meta.byline ? `<p class="byline">${esc(meta.byline)}</p>` : ""}
    ${metaLine ? `<p class="meta">${esc(metaLine)}</p>` : ""}
  </div>
  <div class="body">
${bodyHtml}
  </div>
</article>
</body>
</html>
`;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
function main() {
  const args = process.argv.slice(2);
  if (!args.length) {
    console.error("Bruk: node build-pdf.mjs <fil.md> [flere.md ...]");
    process.exit(1);
  }
  // Output følger serien (kjøres fra serie-mappa); fonts følger scriptet via __dirname.
  const pdfDir = path.join(process.cwd(), "pdf");
  const htmlDir = path.join(pdfDir, "_html");
  fs.mkdirSync(htmlDir, { recursive: true });

  const wp = resolveWeasyprint();
  if (!wp.available) console.warn(wp.hint);

  for (const arg of args) {
    const inPath = path.isAbsolute(arg) ? arg : path.join(process.cwd(), arg);
    if (!fs.existsSync(inPath)) {
      console.error(`Fant ikke: ${inPath}`);
      continue;
    }
    const raw = fs.readFileSync(inPath, "utf8");
    const { meta, body } = parseFrontMatter(raw);
    const base = path.basename(inPath).replace(/\.md$/i, "");
    const html = buildPrintHtml(meta, body);
    const htmlPath = path.join(htmlDir, base + ".html");
    const pdfPath = path.join(pdfDir, base + ".pdf");
    fs.writeFileSync(htmlPath, html, "utf8");
    if (wp.available) {
      execFileSync("weasyprint", [htmlPath, pdfPath], { stdio: ["ignore", "ignore", "inherit"] });
      const kb = (fs.statSync(pdfPath).size / 1024).toFixed(1);
      console.log(`PDF: ${pdfPath} (${kb} KB)`);
    } else {
      console.warn(`  Hoppet over PDF (weasyprint mangler): ${pdfPath}`);
    }
  }
}

if (import.meta.url === `file://${process.argv[1]}`) {
  main();
}
