#!/usr/bin/env node
/**
 * Zet een nieuw review-conceptbestand + screenshot klaar op basis van alleen een URL.
 *
 * Gebruik:
 *   ANTHROPIC_API_KEY=... npm run new-review -- <url> [slug]
 *   (of: ANTHROPIC_API_KEY in .env zetten — zie .env.example — en dan gewoon
 *    `npm run new-review -- <url> [slug]`, of `make review URL=<url> [SLUG=slug]`
 *    om na afloop meteen vim te openen op het net aangemaakte bestand)
 *
 * Wat het doet:
 *   1. Screenshot van de site (Playwright) -> src/assets/reviews/<slug>/screenshot.png
 *      (de reviewpagina toont deze automatisch als hero bovenaan, gelinkt naar de
 *      website, zodra het bestand bestaat — zie reviews/[slug].astro)
 *   2. Titel/meta/zichtbare tekst van de site scrapen
 *   3. Claude (Haiku) laten samenvatten tot titel/bedrijfsnaam/categorie (één woord,
 *      bepaalt ook de coverkleur en de sectie op /categorieen)/doelgroep/prijsindicatie
 *      — met expliciete instructie om niets te verzinnen, onbekend = leeg veld
 *   4. Claude laat ook een concepttekst schrijven voor de secties "Voor wie",
 *      "Wie zit erachter" en "Prijs" (gebaseerd op de site-tekst, gemarkeerd
 *      als AI-concept)
 *   5. Nieuw src/content/reviews/<slug>.md wegschrijven vanuit _template.md, met die velden ingevuld
 *
 * Wat het BEWUST NIET automatisch invult:
 *   score, verdict, "Wat is het" en "Hands-on indruk" — dat is jouw eigen testervaring
 *   na het zelf proberen, niet iets wat een script (of AI die de website leest) kan
 *   beoordelen. Alle AI-ingevulde velden/secties krijgen een "AI-concept, controleren!"-
 *   markering: check ze na.
 */
import { chromium } from "playwright";
import Anthropic from "@anthropic-ai/sdk";
import { mkdir, readFile, writeFile, access, readdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, "..");

function slugify(hostname) {
  return hostname
    .replace(/^www\./, "")
    .replace(/\.(com|nl|ai|io|app|co)$/i, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/(^-|-$)/g, "");
}

/** YYYY-MM-DD in lokale tijd (middaguur om DST-randen te vermijden). */
function formatYmd(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

function parseYmd(s) {
  const [y, m, d] = s.split("-").map(Number);
  return new Date(y, m - 1, d, 12, 0, 0, 0);
}

/** Schuif door tot de eerstvolgende vrijdag (blijft staan als 't al vrijdag is). */
function toFriday(d) {
  const out = new Date(d);
  const day = out.getDay(); // 0 zo … 5 vr
  if (day !== 5) out.setDate(out.getDate() + ((5 - day + 7) % 7));
  return out;
}

/**
 * Publicatiedatum voor een nieuw concept: laatste reviewdatum + 1 week,
 * afgerond naar vrijdag (wekelijkse cadans). Geen eerdere datum? → eerstvolgende vrijdag.
 */
async function nextPublishDate() {
  const dir = path.join(projectRoot, "src", "content", "reviews");
  const files = await readdir(dir);
  let latest = null;

  for (const file of files) {
    if (!file.endsWith(".md") || file.startsWith("_")) continue;
    const text = await readFile(path.join(dir, file), "utf-8");
    // Alleen een echte frontmatter-date: "date: 2026-07-31" (geen commentaarregel)
    const match = text.match(/^---\r?\n[\s\S]*?^date:\s*(\d{4}-\d{2}-\d{2})\b/m);
    if (!match) continue;
    if (!latest || match[1] > latest) latest = match[1];
  }

  if (latest) {
    const d = parseYmd(latest);
    d.setDate(d.getDate() + 7);
    return formatYmd(toFriday(d));
  }

  return formatYmd(toFriday(new Date()));
}

function fillField(template, field, value) {
  // Laat de regel (met het voorbeeld in de comment) ongewijzigd als er niets gevonden is,
  // zodat de gebruiker nog steeds ziet wat er verwacht wordt en het zelf kan invullen.
  if (!value) return template;
  const re = new RegExp(`^${field}: ""(\\s*#.*)?$`, "m");
  return template.replace(re, `${field}: ${JSON.stringify(value)}  # AI-concept, controleren!`);
}

function fillSection(template, heading, placeholderComment, prose) {
  // Zelfde idee als fillField, maar dan voor een hele body-sectie: als er niets
  // gevonden is, blijft de originele hint-comment gewoon staan voor handmatig invullen.
  if (!prose) return template;
  const search = `## ${heading}\n\n<!-- ${placeholderComment} -->`;
  const replacement = `## ${heading}\n\n${prose}\n\n<!-- AI-concept, controleren! -->`;
  return template.replace(search, replacement);
}

async function main() {
  const [, , urlArg, slugArg] = process.argv;
  if (!urlArg) {
    console.error("Gebruik: npm run new-review -- <url> [slug]");
    process.exit(1);
  }

  let url;
  try {
    url = new URL(urlArg).toString();
  } catch {
    console.error(`Ongeldige URL: ${urlArg}`);
    process.exit(1);
  }

  const slug = slugArg || slugify(new URL(url).hostname);
  const contentPath = path.join(projectRoot, "src", "content", "reviews", `${slug}.md`);

  try {
    await access(contentPath);
    console.error(
      `Bestaat al: ${path.relative(projectRoot, contentPath)} — kies een andere slug of verwijder 'm eerst.`,
    );
    process.exit(1);
  } catch {
    // bestaat nog niet, prima
  }

  if (!process.env.ANTHROPIC_API_KEY) {
    console.error("ANTHROPIC_API_KEY niet gezet. Voorbeeld:");
    console.error(`  ANTHROPIC_API_KEY=sk-ant-... npm run new-review -- ${urlArg}`);
    process.exit(1);
  }

  console.error(`Site laden: ${url}`);
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

  try {
    await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
  } catch {
    await page.goto(url, { waitUntil: "load", timeout: 30_000 });
  }

  const outDir = path.join(projectRoot, "src", "assets", "reviews", slug);
  await mkdir(outDir, { recursive: true });
  const screenshotPath = path.join(outDir, "screenshot.png");
  await page.screenshot({ path: screenshotPath });
  console.error(`Screenshot: ${path.relative(projectRoot, screenshotPath)}`);

  const pageTitle = await page.title();
  const metaDescription = await page
    .locator('meta[name="description"]')
    .first()
    .getAttribute("content")
    .catch(() => null);
  const bodyText = (await page.locator("body").innerText()).slice(0, 8000);

  await browser.close();

  console.error("Claude laten meekijken voor de basisinfo...");
  const anthropic = new Anthropic();

  const extraction = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 2048,
    tools: [
      {
        name: "record_tool_info",
        description: "Leg de basisinformatie van deze tool vast voor een reviewtemplate.",
        input_schema: {
          type: "object",
          properties: {
            title: {
              type: "string",
              description: "Nederlandse review-titel in de stijl 'Bedrijfsnaam: wat het doet in één zin'",
            },
            company: { type: "string", description: "Naam van het bedrijf/product" },
            location: {
              type: "string",
              description: "Vestigingsplaats, ALLEEN als die letterlijk ergens op de site staat, anders lege string",
            },
            category: {
              type: "string",
              description:
                "Categorie in ÉÉN Nederlands woord (bepaalt ook de sectie op de categorieënpagina), bv. 'Klantenservice', 'Boekhouding', 'Marketing', 'Juridisch'. Geen meerwoordige omschrijvingen.",
            },
            for_who: { type: "string", description: "Korte typering van de doelgroep, gebaseerd op de site-tekst" },
            price_from: {
              type: "string",
              description:
                "Laagste genoemde prijs incl. periode, bv. '€39 /maand', ALLEEN als een bedrag op de site staat, anders lege string",
            },
            hosting: {
              type: "string",
              description:
                "Korte typering van hosting/privacy ALLEEN als expliciet vermeld (bv. 'EU, GDPR-verklaring'), anders lege string",
            },
            for_who_prose: {
              type: "string",
              description:
                "1-3 zinnen Nederlandse lopende tekst voor de sectie 'Voor wie': welk type gebruiker/bedrijf dit relevant vindt, en voor wie juist niet. Gebaseerd op de site-tekst, niet verzinnen. Lege string als er te weinig te zeggen valt.",
            },
            company_info_prose: {
              type: "string",
              description:
                "1-3 zinnen Nederlandse lopende tekst voor de sectie 'Wie zit erachter': team/locatie/funding/omzet ALLEEN als dat ergens op de site staat. Lege string als er niets over te vinden is (niet gokken of aanvullen met algemene kennis).",
            },
            price_prose: {
              type: "string",
              description:
                "1-3 zinnen Nederlandse lopende tekst voor de sectie 'Prijs': iets uitgebreider dan een los bedrag, bv. tiers, gratis laag, wat er wel/niet in zit — ALLEEN gebaseerd op wat er echt op de site staat. Lege string als er geen prijsinformatie te vinden is.",
            },
          },
          required: [
            "title",
            "company",
            "location",
            "category",
            "for_who",
            "price_from",
            "hosting",
            "for_who_prose",
            "company_info_prose",
            "price_prose",
          ],
        },
      },
    ],
    tool_choice: { type: "tool", name: "record_tool_info" },
    messages: [
      {
        role: "user",
        content:
          "Hier is de inhoud van een bedrijfswebsite, voor een conceptreview op een AI-tool-blog. " +
          "Haal er alleen op wat er ECHT letterlijk staat, verzin nooit feiten (prijzen, locatie, " +
          "team). Onbekende/niet-vermelde velden: lege string, niet gokken. Voor de lopende-tekst-" +
          "velden (for_who_prose/company_info_prose/price_prose) mag je de site-tekst wel in eigen " +
          "woorden samenvatten, in de rustige/nuchtere toon van de rest van de site — maar blijf bij " +
          "wat de site zelf claimt, voeg geen eigen mening of hands-on-oordeel toe.\n\n" +
          `Paginatitel: ${pageTitle}\n` +
          `Meta-omschrijving: ${metaDescription ?? ""}\n\n` +
          `Zichtbare tekst van de pagina:\n${bodyText}`,
      },
    ],
  });

  const toolUse = extraction.content.find((block) => block.type === "tool_use");
  if (!toolUse) {
    console.error("Kon geen structured info uit het Claude-antwoord halen.");
    process.exit(1);
  }
  const info = toolUse.input;

  const templatePath = path.join(projectRoot, "src", "content", "reviews", "_template.md");
  let content = await readFile(templatePath, "utf-8");

  // instructie-comment bovenaan het template hoort niet in een echt reviewbestand
  content = content.replace(/# Kopieer dit bestand[\s\S]*?koppeltekens\.\n\n/, "");

  // Publicatiedatum: laatste bestaande review + 1 week, op vrijdag (cadans).
  // draft blijft true tot je zelf klaar bent; de datum staat al klaar voor planning.
  const publishDate = await nextPublishDate();
  content = content.replace(
    /^date: ""(\s*#.*)?$/m,
    `date: ${publishDate}       # auto: laatste review + 1 week (vrijdag)`,
  );
  console.error(`Publicatiedatum: ${publishDate} (laatste review + 1 week, vrijdag)`);

  // score: 0 is ongeldig voor het Zod-schema (min(1)) — uitcommentariëren zodat
  // het veld afwezig is (optional zolang draft) maar de invulinstructie blijft.
  content = content.replace(
    /^score: 0(\s*#.*)?$/m,
    "# score: 3              # geheel getal 1-5 (aantal robots) — invullen vóórdat je draft: false zet",
  );

  content = content.replace(/^website: ""(\s*#.*)?$/m, `website: ${JSON.stringify(url)}`);
  content = fillField(content, "title", info.title);
  content = fillField(content, "company", info.company);
  content = fillField(content, "location", info.location);
  content = fillField(content, "category", info.category);
  content = fillField(content, "for_who", info.for_who);
  content = fillField(content, "price_from", info.price_from);
  content = fillField(content, "hosting", info.hosting);

  content = fillSection(
    content,
    "Voor wie",
    "Voor welk type gebruiker/bedrijf is dit relevant, en voor wie juist niet.",
    info.for_who_prose,
  );
  content = fillSection(
    content,
    "Wie zit erachter",
    "Team, locatie, funding/omzet indien bekend. Onbekend? Dat gewoon zo benoemen.",
    info.company_info_prose,
  );
  content = fillSection(
    content,
    "Prijs",
    "Prijsmodel iets uitgebreider dan het specs-balkje: tiers, gratis laag, addertjes onder het gras.",
    info.price_prose,
  );

  // Geen screenshot-markdown meer in de body: de reviewpagina toont 'm zelf al
  // automatisch als hero bovenaan (gelinkt naar de website) zodra
  // src/assets/reviews/<slug>/screenshot.png bestaat, zie reviews/[slug].astro.

  await writeFile(contentPath, content, "utf-8");
  console.error(`Concept aangemaakt: ${path.relative(projectRoot, contentPath)}`);
  console.error(
    "Nog met de hand te doen: elk 'AI-concept, controleren!'-veld/sectie naast lezen, en zelf 'Wat is het', score, verdict en hands-on indruk schrijven na het testen.",
  );

  // enige stdout-regel, bedoeld om door scripts (bv. de Makefile) opgepakt te worden
  console.log(contentPath);
}

main();
