// Ralph-loop self-test for the chip-fetcher dashboard. Drives the REAL UI in
// headless Chromium at a phone viewport, exercises the source carat + zoom, and
// asserts + screenshots so regressions are caught here, not on John's phone.
//
//   node chip-fetcher/selftest.cjs            # full run
// Screenshots → /tmp/selftest/*.png
const puppeteer = require('/home/adom/project/node_modules/puppeteer-core');
const fs = require('fs');

const CHROME = '/home/adom/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome';
const BASE = 'http://127.0.0.1:8786/';
const OUT = '/tmp/selftest';
const MPN = process.argv[2] || 'BMI270';
const PROJ = process.argv[3] || 'Multi-source demo 2 (BMI270)';

const sleep = (ms) => new Promise(r => setTimeout(r, ms));
fs.mkdirSync(OUT, { recursive: true });
const checks = [];
const check = (name, ok, detail = '') => { checks.push({ name, ok, detail }); console.log(`${ok ? 'PASS' : 'FAIL'}  ${name}${detail ? ' — ' + detail : ''}`); };

(async () => {
  const browser = await puppeteer.launch({ executablePath: CHROME, headless: 'new', args: ['--no-sandbox', '--disable-gpu'] });
  const page = await browser.newPage();
  await page.setViewport({ width: 390, height: 844, deviceScaleFactor: 2, isMobile: true, hasTouch: true });
  page.on('pageerror', e => console.log('PAGEERROR', e.message));

  const url = `${BASE}?project=${encodeURIComponent(PROJ)}&chip=${encodeURIComponent(MPN)}`;
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
  await page.waitForSelector('.card', { timeout: 15000 });
  await sleep(1200);
  await page.screenshot({ path: `${OUT}/1-card.png` });

  // The deep-linked chip is present + filtered.
  const cards = await page.$$eval('.card', els => els.map(e => e.getAttribute('data-mpn')));
  check('deep-link filters to the chip', cards.length === 1 && cards[0] === MPN, `cards=${JSON.stringify(cards)}`);

  // Footprint carat: list sources + the canonical (current) FP thumbnail must NOT
  // be the ds2sf one (the bug John found).
  const fpInfo = await page.evaluate(() => {
    const grp = [...document.querySelectorAll('.thumb-src-group')].find(g => g.querySelector('.thumb[data-thumb-kind="footprint"]'));
    if (!grp) return { err: 'no footprint group' };
    const chip = grp.querySelector('.src-chip-lab')?.textContent;
    const img = grp.querySelector('.thumb img');
    const opts = [...grp.querySelectorAll('.src-opt')].map(o => o.querySelector('.src-opt-lab').textContent);
    return { chip, imgSrc: img?.src, opts };
  });
  check('FP carat lists multiple sources', (fpInfo.opts || []).length >= 3, `opts=${JSON.stringify(fpInfo.opts)}`);
  check('FP tile defaults to a vendor source (not ds2sf)', /SnapEDA|Ultra|Component/.test(fpInfo.chip || ''), `chip=${fpInfo.chip}`);
  // ds2sf with no pads must NOT appear as a footprint source.
  check('ds2sf not listed as a FP source when it has no footprint', !(fpInfo.opts || []).some(o => /ds2sf/i.test(o)), `opts=${JSON.stringify(fpInfo.opts)}`);

  // "Open in" menu (detail panel) exposes every tool for the chip.
  const tools = await page.$$eval('.openin-row .openbtn', els => els.map(e => e.textContent));
  check('"Open in" lists adom-symbol/footprint/lbr/3D', ['adom-symbol', 'adom-footprint', 'adom-lbr', 'adom-3D'].every(t => tools.includes(t)), `tools=${JSON.stringify(tools)}`);
  check('"Open in" includes chipsmith', tools.includes('chipsmith'));
  check('"Open in" includes ds2sf extraction', tools.includes('ds2sf extraction'));

  // Detail panel is tabbed; Actions is the default (Files must NOT dominate).
  const tabs = await page.evaluate(() => {
    const t = [...document.querySelectorAll('.dtab')].map(x => x.textContent.trim());
    const onTab = document.querySelector('.dtab.on')?.textContent.trim();
    const filesPanelVisible = document.querySelector('.dpanel[data-panel="files"]')?.classList.contains('on');
    return { t, onTab, filesPanelVisible };
  });
  check('detail panel is tabbed (Actions/Details/Files)', tabs.t.some(x => x.startsWith('Actions')) && tabs.t.some(x => x.startsWith('Files')), `tabs=${JSON.stringify(tabs.t)}`);
  check('Actions is the default tab (Files not dominating)', /Actions/.test(tabs.onTab || '') && !tabs.filesPanelVisible, `onTab=${tabs.onTab}`);
  await page.evaluate(() => document.querySelector('.openin-row')?.scrollIntoView());
  await page.screenshot({ path: `${OUT}/4-detail-tabs.png` });

  // The canonical FP thumbnail must actually load (not broken) and not be ds2sf-synth.
  const fpThumb = await page.evaluate(async () => {
    const grp = [...document.querySelectorAll('.thumb-src-group')].find(g => g.querySelector('.thumb[data-thumb-kind="footprint"]'));
    const img = grp.querySelector('.thumb img');
    const ok = img.complete && img.naturalWidth > 0;
    let body = '';
    try { body = await (await fetch(img.src)).text(); } catch {}
    return { ok, hasDs2sf: /ds2sf|SYNTHESI/i.test(body) };
  });
  check('canonical FP thumbnail loads (not broken)', fpThumb.ok);
  check('canonical FP thumbnail is NOT ds2sf-synthesized', !fpThumb.hasDs2sf);

  // Switch the FP source via the carat → label sticks + tile re-renders.
  await page.evaluate(() => {
    const grp = [...document.querySelectorAll('.thumb-src-group')].find(g => g.querySelector('.thumb[data-thumb-kind="footprint"]'));
    grp.querySelector('.src-chip').click();
  });
  await sleep(300);
  const switched = await page.evaluate(() => {
    const pop = [...document.querySelectorAll('.src-popover')].find(p => p.offsetParent !== null);
    const ul = [...pop.querySelectorAll('.src-opt')].find(o => /Ultra/.test(o.textContent));
    ul.click();
    const grp = ul.closest('.thumb-src-group');
    return grp.querySelector('.src-chip-lab').textContent;
  });
  await sleep(400);
  check('picking a source updates the carat label (sticks)', /Ultra/.test(switched), `label=${switched}`);
  await page.screenshot({ path: `${OUT}/2-source-switched.png` });

  // Zoom: tap the zoom button → full-screen overlay with a visible X.
  await page.evaluate(() => { document.querySelector('.card .zoom-btn').click(); });
  await sleep(700);
  const zoom = await page.evaluate(() => {
    const bd = document.querySelector('.card-zoom-backdrop');
    const x = document.querySelector('.card-zoom-x');
    const wrap = document.querySelector('.card-zoomed-wrapper');
    const fills = wrap ? wrap.getBoundingClientRect().width / window.innerWidth : 0;
    return { hasBackdrop: !!bd, fullscreen: bd?.classList.contains('fullscreen'), hasX: !!x && getComputedStyle(x).display !== 'none', widthFrac: fills };
  });
  check('zoom opens an overlay', zoom.hasBackdrop);
  check('zoom is full-screen on phone', zoom.fullscreen && zoom.widthFrac > 0.85, `widthFrac=${zoom.widthFrac.toFixed(2)}`);
  check('zoom has a visible X close button', zoom.hasX);
  // The zoom focus view exposes Open-in + Send-to (reachable on a phone).
  const zoomActions = await page.evaluate(() => ({
    open: [...document.querySelectorAll('.card-zoomed-wrapper .openin-row .openbtn')].map(b => b.textContent),
    send: [...document.querySelectorAll('.card-zoomed-wrapper .sendto-row .sendbtn')].map(b => b.textContent),
  }));
  check('zoom view has Open-in actions', zoomActions.open.includes('adom-symbol') && zoomActions.open.includes('chipsmith'), `open=${JSON.stringify(zoomActions.open)}`);
  check('zoom view has Send-to actions', zoomActions.send.length === 4, `send=${JSON.stringify(zoomActions.send)}`);
  await page.screenshot({ path: `${OUT}/3-zoom.png` });

  // Tapping "Open in adom-symbol" from the zoom actually opens a NEW TAB
  // (window.open) — the phone-correct method.
  const before = (await browser.pages()).length;
  await page.evaluate(() => [...document.querySelectorAll('.card-zoomed-wrapper .openin-row .openbtn')].find(b => b.textContent === 'adom-symbol').click());
  await sleep(2500);
  const after = (await browser.pages()).length;
  check('opening adom-symbol spawns a new browser tab (window.open)', after > before, `pages ${before}→${after}`);
  const toast = await page.evaluate(() => document.getElementById('cf-toast')?.textContent || '');
  check('user is told the open method', /new browser tab|window\.open|Hydrogen/i.test(toast), `toast="${toast.trim()}"`);

  // X closes it.
  await page.evaluate(() => document.querySelector('.card-zoom-x').click());
  await sleep(500);
  const closed = await page.$('.card-zoom-backdrop');
  check('X closes the zoom (back to index)', !closed);

  await browser.close();
  const fails = checks.filter(c => !c.ok);
  console.log(`\n${checks.length - fails.length}/${checks.length} checks passed.`);
  fs.writeFileSync(`${OUT}/result.json`, JSON.stringify(checks, null, 2));
  process.exit(fails.length ? 1 : 0);
})().catch(e => { console.error('SELFTEST ERROR', e); process.exit(2); });