#!/usr/bin/env node
/**
 * tools/dnd-smoke.mjs — CSR (Path B / drag&drop) load smoke.
 *
 * The per-format perf-smoke only exercised Path A (?model=). This drives the
 * REAL local-file path: fetch each sample into a File, hand it to
 * window.__viewer.loadLocalFile(file) (exactly what the dropzone does), and wait
 * for hmw:ready. Catches the blob:-URL-has-no-extension class of bug.
 *
 * USAGE: node tools/dnd-smoke.mjs [--url URL] [--asset /samples/x ...]
 *   (run `npm run build && npm run preview` first)
 */
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 a = argv.slice(2); let url = 'http://127.0.0.1:4173'; const assets = [];
  for (let i = 0; i < a.length; i++) {
    if (a[i] === '--url') url = a[++i]; else if (a[i] === '--asset') assets.push(a[++i]);
  }
  if (!assets.length) assets.push('/samples/Box.glb', '/samples/Duck.glb', '/samples/Avocado.glb', '/samples/Cube.obj', '/samples/Cube.fbx', '/samples/Cube.dae', '/samples/Cube.ifc');
  return { url, assets };
}

async function dropOne(page, base, asset) {
  await page.goto(base, { waitUntil: 'domcontentloaded', timeout: 30000 });
  await page.waitForFunction('window.__viewer != null', { timeout: 15000 });
  try {
    const res = await page.evaluate(async (assetPath) => {
      const name = assetPath.split('/').pop();
      const resp = await fetch(assetPath);
      if (!resp.ok) return { ok: false, err: 'fetch ' + resp.status };
      const blob = await resp.blob();
      const file = new File([blob], name, { type: blob.type || 'application/octet-stream' });
      const ready = new Promise((resolve) => {
        const onErr = (e) => resolve({ ok: false, err: 'viewer error: ' + (e.detail || 'unknown') });
        window.addEventListener('hmw:ready', () => resolve({ ok: true }), { once: true });
        // surface our own onError via console; also time out below
        window.__dndTimeout = setTimeout(() => resolve({ ok: false, err: 'timeout (no hmw:ready)' }), 20000);
      });
      window.__viewer.loadLocalFile(file);
      return ready;
    }, asset);
    return { asset, ...res };
  } catch (e) {
    return { asset, ok: false, err: (e && e.message) || String(e) };
  }
}

async function main() {
  const o = parseArgs(process.argv);
  const exe = findBrowser();
  if (!exe) { console.error('[dnd-smoke] No Chrome/Edge found.'); process.exit(1); }
  const browser = await puppeteer.launch({ executablePath: exe, headless: 'new', args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-webgl', '--ignore-gpu-blocklist'] });
  const page = await browser.newPage();
  const errs = [];
  page.on('console', (m) => { const t = m.text(); if (t.includes('load failed') || t.includes('Failed to load')) errs.push(t); });

  console.log('[dnd-smoke] CSR drag&drop path, base=' + o.url);
  const results = [];
  for (const asset of o.assets) {
    process.stdout.write('  drop ' + asset + ' ... ');
    const r = await dropOne(page, o.url, asset);
    results.push(r);
    console.log(r.ok ? 'OK' : 'FAIL (' + r.err + ')');
  }
  await browser.close();

  const failed = results.filter((r) => !r.ok);
  console.log('\n[dnd-smoke] ' + (results.length - failed.length) + '/' + results.length + ' passed');
  if (failed.length) { console.error('[dnd-smoke] FAILURES: ' + failed.map((f) => f.asset).join(', ')); process.exit(1); }
  console.log('[dnd-smoke] all local drops load.');
}
main().catch((e) => { console.error('[dnd-smoke] Fatal: ' + (e && e.stack ? e.stack : e)); process.exit(1); });
