// Article screenshots — captures a thumbnail of a coverage article's page
// using the SAME local Chrome/Chromium the PDF renderer uses. Zero deps.
// Shots are cached on disk by URL hash, so each article is captured once.

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
export const SHOTS_DIR = path.join(__dirname, '..', 'reports', 'shots');

const CANDIDATES = [
  process.env.CHROME_PATH,
  '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
  '/Applications/Chromium.app/Contents/MacOS/Chromium',
  'google-chrome', 'chromium', 'chromium-browser',
].filter(Boolean);

let workingBin; // remembered after the first success

const hashUrl = (u) => crypto.createHash('sha1').update(String(u)).digest('hex').slice(0, 20);

function tryCapture(bin, url, file, timeoutMs) {
  return new Promise((resolve) => {
    const child = spawn(bin, [
      '--headless=new', '--disable-gpu', '--no-sandbox', '--no-first-run',
      '--hide-scrollbars', '--mute-audio', '--disable-extensions',
      '--window-size=560,780', '--virtual-time-budget=7000',
      `--screenshot=${file}`, url,
    ], { stdio: 'ignore' });
    const timer = setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* gone */ } resolve(false); }, timeoutMs);
    child.on('exit', () => {
      clearTimeout(timer);
      try { resolve(fs.existsSync(file) && fs.statSync(file).size > 4000); } catch { resolve(false); }
    });
    child.on('error', () => { clearTimeout(timer); resolve(false); });
  });
}

// Capture one article → PNG path (cached), or null when capture isn't possible.
export async function articleShot(url, { timeoutMs = 15000 } = {}) {
  if (!/^https?:\/\//i.test(String(url || ''))) return null;
  fs.mkdirSync(SHOTS_DIR, { recursive: true });
  const file = path.join(SHOTS_DIR, hashUrl(url) + '.png');
  if (fs.existsSync(file) && fs.statSync(file).size > 4000) return file;
  const bins = workingBin ? [workingBin] : CANDIDATES;
  for (const bin of bins) {
    if (await tryCapture(bin, url, file, timeoutMs)) { workingBin = bin; return file; }
  }
  return null;
}

// Capture several articles with limited concurrency; returns url → data URI.
export async function articleShotsDataUris(urls, { concurrency = 2, timeoutMs = 15000, max = 6 } = {}) {
  const list = [...new Set(urls.filter((u) => /^https?:\/\//i.test(String(u || ''))))].slice(0, max);
  const out = {};
  let i = 0;
  const workers = Array.from({ length: Math.min(concurrency, list.length) }, async () => {
    while (i < list.length) {
      const url = list[i++];
      const file = await articleShot(url, { timeoutMs }).catch(() => null);
      if (file) {
        try { out[url] = 'data:image/png;base64,' + fs.readFileSync(file).toString('base64'); }
        catch { /* unreadable */ }
      }
    }
  });
  await Promise.all(workers);
  return out;
}
