/**
 * scripts/generate-blog-images.js
 *
 * Generates branded 1200x630 PNG hero images for every blog article
 * (and one for the blog hub page). Renders SVG via sharp — same visual
 * language as the existing /public/og/*.png set.
 *
 * Re-run any time the article set changes. Idempotent: overwrites existing
 * files in /public/og/blog/.
 *
 *   node scripts/generate-blog-images.js
 *
 * No CLI args. The article set is read from scripts/seed-blog.js (the
 * single source of truth for blog content). If that file can't be required
 * (e.g. before the seed is written), the script exits cleanly — it can
 * always be re-run later.
 */

'use strict';

const fs   = require('fs');
const path = require('path');
const sharp = require('sharp');

const PUBLIC_OG_DIR = path.join(__dirname, '..', 'public', 'og', 'blog');
const OG_HUB_PATH   = path.join(__dirname, '..', 'public', 'og', 'blog.png');

// Article visual motifs — one per category, drawn as a small monogram in the
// bottom-left of the hero. SVG primitives only, no external assets.
const CATEGORY_MOTIFS = {
  'Safety Guide': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <path d="M0 -32 L0 4 C0 18 16 30 32 30 C48 30 64 18 64 4 L64 -32 Z"/>
      <path d="M22 6 L30 14 L46 -4"/>
    </g>`,
  'Regulatory': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <path d="M14 -32 L50 -32 L50 26 L14 26 Z"/>
      <line x1="14" y1="-32" x2="14" y2="-22"/>
      <line x1="50" y1="-32" x2="50" y2="-22"/>
      <line x1="22" y1="-12" x2="42" y2="-12"/>
      <line x1="22" y1="0" x2="42" y2="0"/>
      <line x1="22" y1="12" x2="38" y2="12"/>
    </g>`,
  'Legal': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="32" cy="-12" r="20"/>
      <path d="M20 -12 L28 -12 L28 -4 L36 -4 L36 -12 L44 -12"/>
      <line x1="20" y1="2" x2="44" y2="2"/>
      <line x1="20" y1="12" x2="44" y2="12"/>
    </g>`,
  'Creators': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="32" cy="-14" r="10"/>
      <path d="M14 24 C14 8 50 8 50 24"/>
      <path d="M52 -22 L62 -22 L62 -12"/>
      <path d="M62 -22 L52 -12"/>
    </g>`,
  'Technical': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <rect x="2"  y="-28" width="20" height="20" rx="2"/>
      <rect x="42" y="-28" width="20" height="20" rx="2"/>
      <rect x="2"  y="2"   width="20" height="20" rx="2"/>
      <rect x="42" y="2"   width="20" height="20" rx="2"/>
      <line x1="22" y1="-18" x2="42" y2="-18"/>
      <line x1="22" y1="12"  x2="42" y2="12"/>
      <line x1="12" y1="-8"  x2="12" y2="2"/>
      <line x1="52" y1="-8"  x2="52" y2="2"/>
    </g>`,
  'Advocacy': `
    <g transform="translate(72, 432)" fill="none" stroke="#0EA5E9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="22" cy="-12" r="10"/>
      <circle cx="42" cy="-12" r="10"/>
      <path d="M4 24 C4 8 28 8 32 16"/>
      <path d="M60 24 C60 8 36 8 32 16"/>
      <line x1="32" y1="16" x2="32" y2="32"/>
    </g>`,
};

/**
 * Escape text for safe inclusion in an SVG string. Only the small set of
 * characters that break XML need handling — apostrophes/quotes don't
 * because we use double-quoted SVG attributes and no JavaScript context.
 */
function xmlEsc(s) {
  return String(s == null ? '' : s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

/**
 * Wrap a title across multiple lines so it fits the 1200x630 canvas.
 * Returns an array of <tspan> strings with explicit y offsets.
 *
 * Goal: ~50–58px font, max ~3 lines, lines wrap on word boundaries so
 * no words are split mid-letter. Greedy algorithm; if a single word is
 * still too long it gets a 40px shrink.
 */
function wrapTitle(text, maxCharsPerLine = 22) {
  const words = String(text || '').split(/\s+/).filter(Boolean);
  const lines = [];
  let cur = '';
  for (const w of words) {
    const candidate = cur ? cur + ' ' + w : w;
    if (candidate.length <= maxCharsPerLine) {
      cur = candidate;
    } else {
      if (cur) lines.push(cur);
      cur = w;
    }
  }
  if (cur) lines.push(cur);
  // Cap at 3 lines for visual balance; the 4th (and beyond) gets pushed
  // into the third with an ellipsis.
  if (lines.length > 3) {
    const kept = lines.slice(0, 3);
    const last = kept[2];
    kept[2] = last.length > maxCharsPerLine - 1 ? last.slice(0, maxCharsPerLine - 1) + '…' : last + '…';
    return kept;
  }
  return lines;
}

/**
 * Build the SVG body for one article hero. Layout (1200x630):
 *   - dark navy background with subtle radial cyan glow top-left
 *   - grid line pattern at 12% opacity (matches marketing.css)
 *   - Shield logo + wordmark top-left
 *   - category pill (cyan, rounded)
 *   - title (DM Sans 60px, white, max 3 lines)
 *   - reading-time + date mono caption
 *   - category motif (line-art icon) bottom-left
 *   - "takedowns.vesamuni.com" URL bottom-right
 */
function buildSvg({ title, category, readingTime, publishedAt, accent }) {
  const lines = wrapTitle(title);
  const lineHeight = 70;
  const startY = 280 - ((lines.length - 1) * lineHeight) / 2;
  const tspans = lines.map((line, i) =>
    `<tspan x="72" y="${startY + i * lineHeight}">${xmlEsc(line)}</tspan>`
  ).join('');

  const motif = CATEGORY_MOTIFS[category] || CATEGORY_MOTIFS['Safety Guide'];

  const dateStr = new Date(publishedAt).toLocaleDateString('en-US', {
    year: 'numeric', month: 'long', day: 'numeric',
  });

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630">
  <defs>
    <radialGradient id="glow" cx="20%" cy="15%" r="60%">
      <stop offset="0%"  stop-color="${accent}" stop-opacity="0.20"/>
      <stop offset="60%" stop-color="${accent}" stop-opacity="0.04"/>
      <stop offset="100%" stop-color="#0B1120" stop-opacity="0"/>
    </radialGradient>
    <pattern id="grid" width="64" height="64" patternUnits="userSpaceOnUse">
      <path d="M 64 0 L 0 0 0 64" fill="none" stroke="#1E293B" stroke-width="1" stroke-opacity="0.35"/>
    </pattern>
    <linearGradient id="pillGrad" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%"  stop-color="${accent}" stop-opacity="0.22"/>
      <stop offset="100%" stop-color="${accent}" stop-opacity="0.10"/>
    </linearGradient>
  </defs>

  <!-- Background -->
  <rect width="1200" height="630" fill="#0B1120"/>
  <rect width="1200" height="630" fill="url(#grid)"/>
  <rect width="1200" height="630" fill="url(#glow)"/>

  <!-- Top accent stripe -->
  <rect x="0" y="0" width="1200" height="3" fill="${accent}" fill-opacity="0.55"/>

  <!-- Top-left brand: logo + wordmark -->
  <g transform="translate(72, 64)" fill="none" stroke="${accent}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
    <path d="M0 0 L0 14 C0 22 9 28 18 28 C27 28 36 22 36 14 L36 0 Z" fill="${accent}" fill-opacity="0.12"/>
  </g>
  <text x="120" y="92" font-family="DM Sans, Inter, system-ui, sans-serif" font-size="22" font-weight="600" fill="#E2E8F0" letter-spacing="0.5">Shield</text>
  <text x="120" y="112" font-family="JetBrains Mono, ui-monospace, monospace" font-size="11" fill="#64748B" letter-spacing="2">NCII SHIELD · VESAMUNI</text>

  <!-- Category pill -->
  <g transform="translate(72, 184)">
    <rect x="0" y="0" width="${12 + category.length * 8.6}" height="36" rx="18" fill="url(#pillGrad)" stroke="${accent}" stroke-opacity="0.55" stroke-width="1"/>
    <text x="${(12 + category.length * 8.6) / 2}" y="23" font-family="JetBrains Mono, ui-monospace, monospace" font-size="13" font-weight="500" fill="${accent}" text-anchor="middle" letter-spacing="1.2">${xmlEsc(category.toUpperCase())}</text>
  </g>

  <!-- Title -->
  <text font-family="DM Sans, Inter, system-ui, sans-serif" font-size="60" font-weight="500" fill="#F8FAFC" letter-spacing="-1.2">
    ${tspans}
  </text>

  <!-- Bottom-left motif -->
  ${motif}

  <!-- Bottom-left caption (next to motif) -->
  <text x="160" y="452" font-family="JetBrains Mono, ui-monospace, monospace" font-size="13" fill="#94A3B8" letter-spacing="1.4">${readingTime} MIN READ  ·  ${xmlEsc(dateStr)}</text>
  <text x="160" y="476" font-family="DM Sans, Inter, system-ui, sans-serif" font-size="14" fill="#64748B" letter-spacing="0.4">Vesamuni Cybersecurity</text>

  <!-- Bottom-right URL -->
  <text x="1128" y="588" font-family="JetBrains Mono, ui-monospace, monospace" font-size="12" fill="#475569" text-anchor="end" letter-spacing="0.6">takedowns.vesamuni.com/blog</text>
</svg>`;
}

/**
 * Hub image — slightly different layout. Brand-heavy, no specific article.
 */
function buildHubSvg() {
  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630">
  <defs>
    <radialGradient id="glow" cx="20%" cy="20%" r="65%">
      <stop offset="0%"  stop-color="#0EA5E9" stop-opacity="0.28"/>
      <stop offset="60%" stop-color="#0EA5E9" stop-opacity="0.05"/>
      <stop offset="100%" stop-color="#0B1120" stop-opacity="0"/>
    </radialGradient>
    <pattern id="grid" width="64" height="64" patternUnits="userSpaceOnUse">
      <path d="M 64 0 L 0 0 0 64" fill="none" stroke="#1E293B" stroke-width="1" stroke-opacity="0.35"/>
    </pattern>
  </defs>

  <rect width="1200" height="630" fill="#0B1120"/>
  <rect width="1200" height="630" fill="url(#grid)"/>
  <rect width="1200" height="630" fill="url(#glow)"/>
  <rect x="0" y="0" width="1200" height="3" fill="#0EA5E9" fill-opacity="0.55"/>

  <!-- Logo -->
  <g transform="translate(72, 64)" fill="none" stroke="#0EA5E9" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
    <path d="M0 0 L0 14 C0 22 9 28 18 28 C27 28 36 22 36 14 L36 0 Z" fill="#0EA5E9" fill-opacity="0.15"/>
  </g>
  <text x="120" y="92" font-family="DM Sans, Inter, system-ui, sans-serif" font-size="22" font-weight="600" fill="#E2E8F0" letter-spacing="0.5">Shield</text>
  <text x="120" y="112" font-family="JetBrains Mono, ui-monospace, monospace" font-size="11" fill="#64748B" letter-spacing="2">NCII SHIELD · VESAMUNI</text>

  <!-- Pill -->
  <g transform="translate(72, 184)">
    <rect x="0" y="0" width="120" height="36" rx="18" fill="#0EA5E9" fill-opacity="0.15" stroke="#0EA5E9" stroke-opacity="0.55" stroke-width="1"/>
    <text x="60" y="23" font-family="JetBrains Mono, ui-monospace, monospace" font-size="13" font-weight="500" fill="#0EA5E9" text-anchor="middle" letter-spacing="1.2">RESOURCE HUB</text>
  </g>

  <!-- Title -->
  <text font-family="DM Sans, Inter, system-ui, sans-serif" font-size="76" font-weight="500" fill="#F8FAFC" letter-spacing="-1.6">
    <tspan x="72" y="300">Practitioner guides</tspan>
    <tspan x="72" y="376" font-family="Instrument Serif, Georgia, serif" font-style="italic" fill="#0EA5E9">&amp; documentation.</tspan>
  </text>

  <!-- Subtitle -->
  <text x="72" y="450" font-family="DM Sans, Inter, system-ui, sans-serif" font-size="20" fill="#94A3B8" letter-spacing="0">NCII takedown workflows, creator leak protection, and evidence preservation.</text>
  <text x="72" y="478" font-family="DM Sans, Inter, system-ui, sans-serif" font-size="20" fill="#94A3B8" letter-spacing="0">Built for survivors, advocates, agencies, and content creators.</text>

  <!-- Footer URL -->
  <text x="1128" y="588" font-family="JetBrains Mono, ui-monospace, monospace" font-size="12" fill="#475569" text-anchor="end" letter-spacing="0.6">takedowns.vesamuni.com/blog</text>
</svg>`;
}

async function svgToPng(svg, outPath) {
  const buf = Buffer.from(svg, 'utf8');
  await sharp(buf, { density: 144 })
    .resize(1200, 630, { fit: 'contain', background: { r: 11, g: 17, b: 32, alpha: 1 } })
    .png({ compressionLevel: 9, palette: undefined })
    .toFile(outPath);
}

async function generateHubImage() {
  const svg = buildHubSvg();
  await svgToPng(svg, OG_HUB_PATH);
  const stat = fs.statSync(OG_HUB_PATH);
  console.log(`[blog-images] hub    ${path.relative(process.cwd(), OG_HUB_PATH)}  (${(stat.size / 1024).toFixed(1)} KB)`);
}

async function generateArticleImages(posts) {
  fs.mkdirSync(PUBLIC_OG_DIR, { recursive: true });

  for (const post of posts) {
    const svg = buildSvg({
      title:       post.title,
      category:    post.category,
      readingTime: post.reading_time_min,
      publishedAt: post.published_at,
      accent:      '#0EA5E9',
    });
    const outPath = path.join(PUBLIC_OG_DIR, `${post.slug}.png`);
    await svgToPng(svg, outPath);
    const stat = fs.statSync(outPath);
    console.log(`[blog-images] article ${path.relative(process.cwd(), outPath)}  (${(stat.size / 1024).toFixed(1)} KB)`);
  }
}

async function main() {
  // Pull the article list from the seed module. If the seed file hasn't been
  // written yet, skip article generation — re-run after the seed exists.
  let seed;
  try {
    seed = require('./seed-blog');
  } catch (e) {
    console.warn('[blog-images] seed-blog.js not loadable yet — generating only the hub image.');
    seed = null;
  }

  await generateHubImage();

  if (seed && Array.isArray(seed.POSTS) && seed.POSTS.length) {
    await generateArticleImages(seed.POSTS);
    console.log(`[blog-images] done. ${seed.POSTS.length} article images + 1 hub.`);
  } else {
    console.log('[blog-images] done. hub only. Re-run after seed-blog.js exists.');
  }
}

main().catch(err => {
  console.error('[blog-images] failed:', err);
  process.exit(1);
});