#!/usr/bin/env node
/**
 * tools/perf-smoke.mjs — Perceived-load smoke test for hmwebviewer (PLAN P5-3).
 *
 * Launches headless Chrome via puppeteer-core (uses the system Chrome; no
 * Chromium download). For each sample asset it loads `?model=<asset>` and
 * measures wall time from navigation-start to the viewer's `hmw:ready` event
 * (dispatched in ThreeDViewer.onLoaded). Asserts < 3000ms perceived.
 *
 * USAGE
 *   node tools/perf-smoke.mjs [--url http://127.0.0.1:4173] [--asset /samples/Box.glb ...]
 *
 * Defaults: url = http://127.0.0.1:4173 (vite preview); assets = the sample set.
 * Run `npm run build && npm run preview` first (or `npm run dev` on its port).
 *
 * Browser auto-detected: Chrome, then Edge, then PUPPETEER_EXECUTABLE_PATH.
 */
import puppeteer from 'puppeteer-core';
import { existsSync } from 'node:fs';

const CHROME_CANDIDATES = [
  'C:/Program Files/Google/Chrome/Application/chrome.exe',
  'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
  'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',
];

function findBrowser() {
  if (process.env.PUPPETEER_EXECUTABLE_PATH && existsSync(process.env.PUPPETEER_EXECUTABLE_PATH)) {
    return process.env.PUPPETEER_EXECUTABLE_PATH;
  }
  for (const p of CHROME_CANDIDATES) if (existsSync(p)) return p;
  return null;
}

function parseArgs(argv) {
  const args = argv.slice(2);
  let url = 'http://127.0.0.1:4173';
  const assets = [];
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--url') url = args[++i];
    else if (args[i] === '--asset') assets.push(args[++i]);
    else if (args[i] === '-h' || args[i] === '--help') return { help: true };
  }
  if (assets.length === 0) {
    assets.push('/samples/Box.glb', '/samples/Duck.glb', '/samples/Duck.optimized.glb', '/samples/Avocado.glb');
  }
  return { url, assets };
}

const THRESHOLD_MS = 3000;

async function measureAsset(browser, baseUrl, asset) {
  const page = await browser.newPage();
  await page.evaluateOnNewDocument(() => {
    window.__hmwReady = null;
    window.addEventListener('hmw:ready', (e) => { window.__hmwReady = e.detail; });
  });
  const target = baseUrl + '?model=' + asset;
  const t0 = Date.now();
  try {
    await page.goto(target, { waitUntil: 'domcontentloaded', timeout: 30000 });
    await page.waitForFunction('window.__hmwReady != null', { timeout: 30000 });
    const data = await page.evaluate(() => ({ readyPerf: window.__hmwReady, origin: performance.timeOrigin }));
    const ms = (data.origin + data.readyPerf) - t0;
    return { asset, ms: Math.round(ms), ok: ms < THRESHOLD_MS };
  } catch (e) {
    return { asset, ms: null, ok: false, error: (e && e.message) ? e.message : String(e) };
  } finally {
    await page.close();
  }
}

async function main() {
  const opts = parseArgs(process.argv);
  if (opts.help) {
    console.error('Usage: node tools/perf-smoke.mjs [--url URL] [--asset PATH ...]');
    process.exit(0);
  }
  const exe = findBrowser();
  if (!exe) {
    console.error('[perf-smoke] No Chrome/Edge found. Set PUPPETEER_EXECUTABLE_PATH.');
    process.exit(1);
  }

  const browser = await puppeteer.launch({
    executablePath: exe,
    headless: 'new',
    args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'],
  });

  console.log('[perf-smoke] base=' + opts.url + '  browser=' + exe);
  console.log('[perf-smoke] threshold=' + THRESHOLD_MS + 'ms perceived');
  const results = [];
  for (const asset of opts.assets) {
    process.stdout.write('  ' + asset + ' ... ');
    const r = await measureAsset(browser, opts.url, asset);
    results.push(r);
    console.log(r.ms === null ? 'FAIL (' + (r.error || 'timeout') + ')' : (r.ms + 'ms ' + (r.ok ? 'OK' : 'OVER')));
  }
  await browser.close();

  console.log('\n[perf-smoke] summary');
  for (const r of results) {
    console.log('  ' + r.asset.padEnd(34) + (r.ms === null ? 'FAIL' : (r.ms + 'ms').padEnd(8)) + (r.ok ? 'PASS' : 'FAIL'));
  }
  const failed = results.filter((r) => !r.ok);
  if (failed.length) {
    console.error('[perf-smoke] ' + failed.length + ' asset(s) over threshold/failed.');
    process.exit(1);
  }
  console.log('[perf-smoke] all within ' + THRESHOLD_MS + 'ms.');
}

main().catch((e) => {
  console.error('[perf-smoke] Fatal: ' + (e && e.stack ? e.stack : e));
  process.exit(1);
});
