#!/usr/bin/env node
// build-carousel.mjs — render en LinkedIn-carousel (dokument-PDF) fra slide-markdown.
// Bruk: node build-carousel.mjs linkedin/06/carousel.md [linkedin/03/carousel.md ...]
// Hver "## SLIDE N — ..." blir én portrett-side (1080×1350, 4:5) i PDF-en.
// Designet typografisk deck — speiler avis-identiteten (Newsreader/Inter, off-white,
// oxblood). Cover (slide 1) + CTA (siste slide) = oxblood-bokstøtter; de øvrige lyse
// med bolk-kicker + footer (valgfri brand + teller). Ingen per-slide AI-foto.
// 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));

// Valgfri brand i footer + cover-eyebrow-fallback. Tom som standard (generisk);
// sett LTL_BRAND til serie-/publikasjonsnavnet for å re-brande (samme mønster
// som LTL_SERIES_ROOT). Tom brand → footer uten brand-span, tom eyebrow-fallback.
const BRAND = process.env.LTL_BRAND || "";

// ---------------------------------------------------------------------------
// 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;
  }
}

// ---------------------------------------------------------------------------
// Inline markdown (**fet**, *kursiv*) + escaping
// ---------------------------------------------------------------------------
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;
}

// ---------------------------------------------------------------------------
// Valgfri YAML front matter (flate key: "value"-par) — for cover/CTA-eyebrow.
// Felt: cover_eyebrow, cta_eyebrow. Faller tilbake til generiske default-er.
// ---------------------------------------------------------------------------
function parseFrontMatter(raw) {
  const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
  if (!m) return { meta: {}, rest: 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, rest: m[2] };
}

// ---------------------------------------------------------------------------
// Parse slide-markdown.
// Slides skilles av "## SLIDE N — label". Innenfor en slide:
//   - linje `…` (backticks)  -> kicker (bolk-merkelapp)
//   - linje "# …"            -> tittel
//   - øvrige ikke-tomme       -> brødtekst-avsnitt (ett per linje)
// ---------------------------------------------------------------------------
function parseSlides(raw) {
  const body = raw.replace(/\r\n/g, "\n");
  // dropp ev. innledende H1 + forklarings-avsnitt før første "## SLIDE"
  const startIdx = body.indexOf("## SLIDE");
  const region = startIdx >= 0 ? body.slice(startIdx) : body;
  const chunks = region.split(/^##\s+SLIDE\b.*$/m).map((c) => c.trim()).filter(Boolean);

  return chunks.map((chunk) => {
    const lines = chunk.split("\n");
    let kicker = null;
    let title = null;
    const bodyParas = [];
    for (const lnRaw of lines) {
      const ln = lnRaw.trim();
      if (!ln) continue;
      if (ln.startsWith("---")) continue;
      const km = ln.match(/^`([^`]+)`$/);
      if (km && !kicker && !title) {
        kicker = km[1].trim();
        continue;
      }
      if (ln.startsWith("# ")) {
        title = ln.slice(2).trim();
        continue;
      }
      bodyParas.push(ln);
    }
    return { kicker, title, bodyParas };
  });
}

// ---------------------------------------------------------------------------
// CSS — portrett 4:5, avis-identitet. Oxblood-bokstøtter for cover/CTA.
// ---------------------------------------------------------------------------
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 CSS = `
${FONT_FACE}
:root{
  --bg:#FBFAF7; --ink:#1A1A1A; --muted:#5b5750; --accent:#9A3324;
  --rule:#d8d4cb; --cream:#F4EFE6;
  --serif:"Newsreader",Georgia,serif;
  --sans:"Inter","Helvetica Neue",Arial,sans-serif;
}
@page{ size:1080px 1350px; margin:0; }
*{ box-sizing:border-box; margin:0; padding:0; }
html,body{ background:var(--bg); }
.slide{
  position:relative;
  width:1080px; height:1350px;
  padding:96px 96px 92px;
  background:var(--bg); color:var(--ink);
  font-family:var(--serif);
  page-break-after:always;
  overflow:hidden;
}
.slide:last-child{ page-break-after:auto; }

/* ---- kicker (bolk-merkelapp) ---- */
.kicker{
  display:inline-block;
  font-family:var(--sans); font-weight:700;
  text-transform:uppercase; letter-spacing:0.14em;
  font-size:22px; color:#fff; background:var(--accent);
  padding:9px 18px; border-radius:3px;
  margin-bottom:46px;
}

/* ---- tittel / brødtekst ---- */
.title{
  font-family:var(--serif); font-weight:700;
  font-size:74px; line-height:1.06; letter-spacing:-0.012em;
  margin-bottom:40px;
}
.body p{
  font-family:var(--serif); font-weight:400;
  font-size:35px; line-height:1.45; color:#2b2823;
  margin-bottom:22px;
}
.body p strong{ font-weight:700; color:var(--ink); }
.body p em{ font-style:italic; }

/* ---- footer ---- */
.footer{
  position:absolute; left:96px; right:96px; bottom:64px;
  display:flex; justify-content:space-between; align-items:center;
  border-top:1px solid var(--rule); padding-top:22px;
  font-family:var(--sans); font-size:21px; letter-spacing:0.04em;
}
.footer .brand{ font-weight:700; text-transform:uppercase; letter-spacing:0.13em; color:var(--accent); }
.footer .count{ color:var(--muted); font-weight:600; }

/* ---- interior layout (grep): topp-justert under kicker ---- */
.slide.interior .stage{ }

/* ---- cover + CTA: oxblood-bokstøtter ---- */
.slide.bookend{
  background:var(--accent); color:var(--cream);
  display:flex; flex-direction:column; justify-content:center;
}
.slide.bookend .eyebrow{
  font-family:var(--sans); font-weight:700; text-transform:uppercase;
  letter-spacing:0.18em; font-size:23px; color:#F0C9B6; margin-bottom:34px;
}
.slide.bookend .title{ color:#fff; font-size:86px; line-height:1.02; margin-bottom:40px; }
.slide.bookend .body p{ color:#F4E4D8; font-size:38px; line-height:1.42; }
.slide.bookend .body p strong{ color:#fff; }
.slide.bookend .footer{
  border-top:1px solid rgba(244,228,216,0.32);
  color:#F0C9B6;
}
.slide.bookend .footer .brand{ color:#fff; }
.slide.bookend .footer .count{ color:#F0C9B6; }
.slide.bookend .arrow{ font-size:40px; }
`;

// ---------------------------------------------------------------------------
// Render én slide til HTML
// ---------------------------------------------------------------------------
function slideHtml(slide, idx, total, eyebrows) {
  const isCover = idx === 0;
  const isCta = idx === total - 1;
  const bookend = isCover || isCta;
  const cls = bookend ? "slide bookend" : "slide interior";

  const titleHtml = slide.title ? `<h1 class="title">${inline(slide.title)}</h1>` : "";
  const bodyHtml = slide.bodyParas.length
    ? `<div class="body">${slide.bodyParas.map((p) => `<p>${inline(p)}</p>`).join("")}</div>`
    : "";

  // kicker: interior bruker bolk-merkelapp; bookend bruker eyebrow (cover/CTA)
  let head = "";
  if (bookend) {
    const eyebrow = isCover ? eyebrows.cover : eyebrows.cta;
    head = `<p class="eyebrow">${esc(eyebrow)}</p>`;
  } else if (slide.kicker) {
    head = `<span class="kicker">${esc(slide.kicker)}</span>`;
  }

  const counter = `${idx + 1} / ${total}`;
  const footer = `<div class="footer">${BRAND ? `<span class="brand">${esc(BRAND)}</span>` : ""}<span class="count">${counter}</span></div>`;

  return `<section class="${cls}">
    ${head}
    ${titleHtml}
    ${bodyHtml}
    ${footer}
  </section>`;
}

function buildHtml(slides, eyebrows) {
  const total = slides.length;
  const slidesHtml = slides.map((s, i) => slideHtml(s, i, total, eyebrows)).join("\n");
  return `<!DOCTYPE html>
<html lang="nb">
<head><meta charset="utf-8"><title>Carousel</title><style>${CSS}</style></head>
<body>
${slidesHtml}
</body>
</html>
`;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
function main() {
  const args = process.argv.slice(2);
  if (!args.length) {
    console.error("Bruk: node build-carousel.mjs <carousel.md> [flere.md ...]");
    process.exit(1);
  }

  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 } = parseFrontMatter(raw);
    const slides = parseSlides(raw);
    if (!slides.length) {
      console.error(`Ingen slides funnet i ${inPath}`);
      continue;
    }
    const eyebrows = {
      cover: meta.cover_eyebrow || BRAND,
      cta: meta.cta_eyebrow || "Kom i gang",
    };
    const dir = path.dirname(inPath);
    const html = buildHtml(slides, eyebrows);
    const htmlPath = path.join(dir, "carousel.html");
    const pdfPath = path.join(dir, "carousel.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(`Carousel: ${pdfPath} (${slides.length} slides, ${kb} KB)`);
    } else {
      console.warn(`  Hoppet over PDF (weasyprint mangler): ${pdfPath}`);
    }
  }
}

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