/**
 * scripts/inline-article-images.js
 *
 * For each blog post, find a copyright-free Wikimedia Commons image
 * relevant to the article body, download it, and inline a <figure>
 * after the first H2 of the article. The user wanted "charts, data,
 * anything" between the paragraphs.
 *
 * Each picked image:
 *   - CC-licensed or public domain
 *   - Landscape, 800-1600px wide
 *   - No "nude", "deepfake face", "explicit" content
 *   - Different from the hero thumbnail (different query)
 *
 * Run:  node scripts/inline-article-images.js
 */

'use strict';

const https = require('https');
const fs    = require('fs');
const path  = require('path');
const sharp = require('sharp');
const { db } = require('../config/db');

const UA = 'takedowns-app/1.6 (https://takedowns.vesamuni.com; admin@takedowns.vesamuni.com)';
const INLINE_DIR = path.join(__dirname, '..', 'public', 'og', 'blog', 'inline');
fs.mkdirSync(INLINE_DIR, { recursive: true });

// Different from the hero queries so the inline picture isn't a duplicate
// of the thumbnail. Also "blocked" filters out the obvious no-go terms.
const TOPICS = [
  ['ncii-takedown-complete-dmca-workflow-guide-2026',                'copyright symbol registered trademark',     []],
  ['how-to-remove-non-consensual-intimate-images-survivor-guide',    'padlock key secure data',                   ['intimate', 'nude']],
  ['onlyfans-dmca-protection-stop-leaks-at-scale',                   'social media platform creator',            ['nude', 'intimate', 'onlyfans']],
  ['fansly-leak-detection-dmca-takedown-creator-workflow',           'watermark copyright protection',            ['nude', 'fansly']],
  ['ai-generated-ncii-deepfakes-legal-landscape-2026',               'neural network diagram',                    ['face', 'nude', 'portrait']],
  ['chain-of-custody-digital-evidence-courts-accept',                'court evidence bag sealed',                []],
  ['reverse-image-search-leak-detection-methodology',                'fingerprint comparison card',              []],
  ['bulk-dmca-dispatch-agency-workflow-1000-takedowns-week',         'postal delivery letter',                   []],
  ['ncii-laws-comparison-us-eu-uk-australia-india',                  'international law scales justice',         []],
  ['webcam-model-protection-chaturbate-stripchat-independent',       'studio microphone recording',              []],
];

function get(url) {
  return new Promise((r, j) => {
    https.get(url, { headers: { 'User-Agent': UA, 'Accept': 'application/json' } }, res => {
      let d = '';
      res.on('data', c => d += c);
      res.on('end', () => r({ status: res.statusCode, body: d }));
    }).on('error', j);
  });
}

function download(url, dest) {
  return new Promise((resolve, reject) => {
    const file = fs.createWriteStream(dest);
    function g(u) {
      https.get(u, { headers: { 'User-Agent': UA } }, res => {
        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return g(res.headers.location);
        if (res.statusCode !== 200) {
          file.close();
          return reject(new Error('HTTP ' + res.statusCode + ' for ' + u));
        }
        res.pipe(file);
        file.on('finish', () => file.close(() => resolve(dest)));
      }).on('error', err => { fs.unlink(dest, () => {}); reject(err); });
    }
    g(url);
  });
}

const FREE_RE = /public domain|^cc0|^cc[- ]by|^cc[- ]by[- ]sa|^cc[- ]by[- ]nc|^cc[- ]by[- ]nd/i;

function pickCandidate(candidates, blocked) {
  for (const c of candidates) {
    if (c.width < 800) continue;
    const ratio = c.width / c.height;
    if (ratio < 1.3 || ratio > 2.4) continue;
    const t = c.title.toLowerCase();
    if (blocked.some(b => t.includes(b))) continue;
    return c;
  }
  return null;
}

async function findCandidates(query, limit = 20) {
  const search = JSON.parse((await get(
    'https://commons.wikimedia.org/w/api.php?action=query&list=search&srsearch=' +
    encodeURIComponent(query) + '&srnamespace=6&format=json&srlimit=' + limit
  )).body);
  if (!search.query || !search.query.search.length) return [];
  const titles = search.query.search.map(r => r.title).join('|');
  const info = JSON.parse((await get(
    'https://commons.wikimedia.org/w/api.php?action=query&titles=' +
    encodeURIComponent(titles) +
    '&prop=imageinfo&iiprop=url|size|mime|extmetadata&iiurlwidth=1600&format=json'
  )).body);
  const pages = info.query.pages;
  const out = [];
  for (const id in pages) {
    const p = pages[id];
    if (!p.imageinfo) continue;
    const ii = p.imageinfo[0];
    const md = ii.extmetadata || {};
    const lic = (md.LicenseShortName?.value || '').trim();
    if (!FREE_RE.test(lic)) continue;
    out.push({
      title: p.title,
      url: ii.url,
      width: ii.width, height: ii.height,
      mime: ii.mime,
      license: lic,
      artist: (md.Artist?.value || '').replace(/<[^>]+>/g, '').slice(0, 120),
      sourceUrl: ii.descriptionurl,
    });
  }
  return out;
}

function escapeHtml(s) {
  return String(s || '').replace(/[<>&"']/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;' })[c]);
}

function makeFigure(slug, att, src) {
  // Inline <figure> with attribution. Width capped in CSS at 800px for
  // reading width. Light glass card around it so it doesn't fight the
  // background.
  return (
    '\n<figure class="article-inline-figure">' +
      '<img src="' + src + '" alt="" loading="lazy" />' +
      '<figcaption>Image: ' + escapeHtml(att) + ' &mdash; <a href="https://commons.wikimedia.org/" rel="noopener" target="_blank">Wikimedia Commons</a></figcaption>' +
    '</figure>\n'
  );
}

function injectAfterFirstH2(html, figure) {
  // Find the FIRST <h2 ...>...</h2>, insert the figure immediately
  // after its closing tag. If no H2 exists, return null so the caller
  // can skip.
  const m = html.match(/(<h2\b[^>]*>[\s\S]*?<\/h2>)/i);
  if (!m) return null;
  const idx = m.index + m[0].length;
  return html.slice(0, idx) + figure + html.slice(idx);
}

async function processTopic(slug, query, blocked) {
  const candidates = await findCandidates(query, 20);
  const pick = pickCandidate(candidates, blocked);
  if (!pick) {
    return { slug, status: 'no-match' };
  }
  const ext = (pick.mime && pick.mime.split('/')[1]) || 'jpg';
  const safeExt = ext === 'jpeg' ? 'jpg' : ['jpg', 'png', 'webp', 'gif'].includes(ext) ? ext : 'jpg';
  const localName = slug + '.' + safeExt;
  const localPath = path.join(INLINE_DIR, localName);
  const relPath = '/public/og/blog/inline/' + localName;

  await download(pick.url, localPath);
  // Resize for in-article: max 1200px wide is plenty for an 800px column
  const tmp = localPath + '.tmp';
  await sharp(localPath).resize({ width: 1200, withoutEnlargement: true }).jpeg({ quality: 85, mozjpeg: true }).toFile(tmp);
  fs.renameSync(tmp, localPath);
  const stat = fs.statSync(localPath);
  if (stat.size < 5_000) throw new Error('too small');

  const attribution = (pick.artist ? pick.artist + ' / ' : '') + pick.license;

  // Inject into body_html
  const post = db.prepare('SELECT body_html FROM blog_posts WHERE slug = ?').get(slug);
  if (!post) return { slug, status: 'db-missing' };
  const figure = makeFigure(slug, attribution, relPath);
  const newBody = injectAfterFirstH2(post.body_html, figure);
  if (!newBody) {
    return { slug, status: 'no-h2-in-body' };
  }
  db.prepare('UPDATE blog_posts SET body_html = ?, updated_at = ? WHERE slug = ?')
    .run(newBody, Date.now(), slug);

  return {
    slug, status: 'ok',
    picked: pick.title, license: pick.license,
    size: pick.width + 'x' + pick.height,
    localFile: relPath, bytes: stat.size,
    attribution,
  };
}

(async () => {
  for (const [slug, q, blocked] of TOPICS) {
    try {
      const r = await processTopic(slug, q, blocked);
      console.log(JSON.stringify(r, null, 0));
    } catch (err) {
      console.log(JSON.stringify({ slug, status: 'error', error: err.message }));
    }
  }
})();
