#!/usr/bin/env node
/**
 * test-spa.js — Deep browser diagnostic for SPA navigation.
 *
 * Uses Chrome DevTools Protocol to inspect event listeners,
 * trace click handling, and detect SPA vs full reload.
 *
 * Usage:
 *   node test-spa.js                  # bytecode mode
 *   node test-spa.js --source         # source mode (nosxbc)
 *   node test-spa.js --headed         # visible browser
 */

const { chromium } = require('playwright');

const args = process.argv.slice(2);
const sourceMode = args.includes('--source');
const headed = args.includes('--headed');
const baseUrl = 'http://localhost:8013/sx/';
const url = sourceMode ? baseUrl + '?nosxbc' : baseUrl;
const label = sourceMode ? 'SOURCE' : 'BYTECODE';

(async () => {
  const browser = await chromium.launch({ headless: !headed });
  const page = await browser.newPage();

  // Capture console
  page.on('console', msg => {
    const t = msg.text();
    if (t.startsWith('[spa-diag]') || t.includes('Not callable') || t.includes('Error:'))
      console.log(`  [browser] ${t}`);
  });

  console.log(`\n=== SPA Diagnostic: ${label} mode ===\n`);
  await page.goto(url);
  await page.waitForTimeout(5000);

  // ----------------------------------------------------------------
  // 1. Use CDP to get event listeners on a link
  // ----------------------------------------------------------------
  console.log('--- 1. Event listeners on Geography link ---');

  const cdp = await page.context().newCDPSession(page);

  const listeners = await page.evaluate(async () => {
    const link = document.querySelector('a[href="/sx/(geography)"]');
    if (!link) return { error: 'link not found' };

    // We can't use getEventListeners from page context (it's a DevTools API)
    // But we can check _sxBound* properties and enumerate own properties
    const ownProps = {};
    for (const k of Object.getOwnPropertyNames(link)) {
      if (k.startsWith('_') || k.startsWith('on'))
        ownProps[k] = typeof link[k];
    }

    // Check for jQuery-style event data
    const jqData = link.__events || link._events || null;

    return {
      href: link.getAttribute('href'),
      ownProps,
      jqData: jqData ? 'present' : 'none',
      onclick: link.onclick ? 'set' : 'null',
      parentTag: link.parentElement?.tagName,
    };
  });
  console.log('  Link props:', JSON.stringify(listeners, null, 2));

  // Check should-boost-link? and why it returns false
  const boostCheck = await page.evaluate(() => {
    const K = window.SxKernel;
    const link = document.querySelectorAll('a[href]')[1]; // geography link
    if (!link) return 'no link';
    try {
      // Check the conditions should-boost-link? checks
      const href = link.getAttribute('href');
      const checks = {
        href,
        hasBoostAttr: link.closest('[data-sx-boost]') ? 'yes' : 'no',
        hasNoBoost: link.hasAttribute('data-sx-no-boost') ? 'yes' : 'no',
        isExternal: href.startsWith('http') ? 'yes' : 'no',
        isHash: href.startsWith('#') ? 'yes' : 'no',
      };
      // Try calling should-boost-link?
      try { checks.shouldBoost = K.eval('(should-boost-link? (nth (dom-query-all (dom-body) "a[href]") 1))'); }
      catch(e) { checks.shouldBoost = 'err: ' + e.message.slice(0, 80); }
      return checks;
    } catch(e) { return 'err: ' + e.message; }
  });
  console.log('  Boost check:', JSON.stringify(boostCheck, null, 2));

  // Use CDP to get actual event listeners
  const linkNode = await page.$('a[href="/sx/(geography)"]');
  if (linkNode) {
    const { object } = await cdp.send('Runtime.evaluate', {
      expression: 'document.querySelector(\'a[href="/sx/(geography)"]\')',
    });
    if (object?.objectId) {
      const { listeners: cdpListeners } = await cdp.send('DOMDebugger.getEventListeners', {
        objectId: object.objectId,
        depth: 0,
      });
      console.log('  CDP event listeners on link:', cdpListeners.length);
      for (const l of cdpListeners) {
        console.log(`    ${l.type}: ${l.handler?.description?.slice(0, 100) || 'native'} (useCapture=${l.useCapture})`);
      }
    }

    // Also check document-level click listeners
    const { object: docObj } = await cdp.send('Runtime.evaluate', {
      expression: 'document',
    });
    if (docObj?.objectId) {
      const { listeners: docListeners } = await cdp.send('DOMDebugger.getEventListeners', {
        objectId: docObj.objectId,
        depth: 0,
      });
      const clickListeners = docListeners.filter(l => l.type === 'click');
      console.log('  CDP document click listeners:', clickListeners.length);
      for (const l of clickListeners) {
        console.log(`    ${l.type}: ${l.handler?.description?.slice(0, 120) || 'native'} (capture=${l.useCapture})`);
      }
    }

    // Check window-level listeners too
    const { object: winObj } = await cdp.send('Runtime.evaluate', {
      expression: 'window',
    });
    if (winObj?.objectId) {
      const { listeners: winListeners } = await cdp.send('DOMDebugger.getEventListeners', {
        objectId: winObj.objectId,
        depth: 0,
      });
      const winClick = winListeners.filter(l => l.type === 'click');
      const winPop = winListeners.filter(l => l.type === 'popstate');
      console.log('  CDP window click listeners:', winClick.length);
      console.log('  CDP window popstate listeners:', winPop.length);
      for (const l of winPop) {
        console.log(`    popstate: ${l.handler?.description?.slice(0, 120) || 'native'}`);
      }
    }
  }

  // ----------------------------------------------------------------
  // 2. Trace what happens when we click
  // ----------------------------------------------------------------
  console.log('\n--- 2. Click trace ---');

  // Inject click tracing
  await page.evaluate(() => {
    // Trace click event propagation
    const phases = ['NONE', 'CAPTURE', 'AT_TARGET', 'BUBBLE'];
    document.addEventListener('click', function(e) {
      console.log('[spa-diag] click CAPTURE on document: target=' + e.target.tagName +
        ' href=' + (e.target.getAttribute?.('href') || 'none') +
        ' defaultPrevented=' + e.defaultPrevented);
    }, true);

    document.addEventListener('click', function(e) {
      console.log('[spa-diag] click BUBBLE on document: defaultPrevented=' + e.defaultPrevented +
        ' propagation=' + (e.cancelBubble ? 'stopped' : 'running'));
    }, false);

    // Monitor pushState
    const origPush = history.pushState;
    history.pushState = function() {
      console.log('[spa-diag] pushState called: ' + JSON.stringify(arguments[2]));
      return origPush.apply(this, arguments);
    };

    // Monitor replaceState
    const origReplace = history.replaceState;
    history.replaceState = function() {
      console.log('[spa-diag] replaceState called: ' + JSON.stringify(arguments[2]));
      return origReplace.apply(this, arguments);
    };
  });

  // Detect full reload vs SPA by checking if a new page load happens
  let fullReload = false;
  let networkNav = false;
  page.on('load', () => { fullReload = true; });
  page.on('request', req => {
    if (req.isNavigationRequest()) {
      networkNav = true;
      console.log('  [network] Navigation request:', req.url());
    }
  });

  // Click the link
  console.log('  Clicking /sx/(geography)...');
  const urlBefore = page.url();
  await page.click('a[href="/sx/(geography)"]');
  await page.waitForTimeout(3000);
  const urlAfter = page.url();

  console.log(`  URL: ${urlBefore.split('8013')[1]} → ${urlAfter.split('8013')[1]}`);
  console.log(`  Full reload: ${fullReload}`);
  console.log(`  Network navigation: ${networkNav}`);

  // Check page content
  const content = await page.evaluate(() => ({
    title: document.title,
    h1: document.querySelector('h1')?.textContent?.slice(0, 50) || 'none',
    bodyLen: document.body.innerHTML.length,
  }));
  console.log('  Content:', JSON.stringify(content));

  // ----------------------------------------------------------------
  // 3. Check SX router state
  // ----------------------------------------------------------------
  console.log('\n--- 3. SX router state ---');
  const routerState = await page.evaluate(() => {
    const K = window.SxKernel;
    if (!K) return { error: 'no kernel' };
    const checks = {};
    try { checks['_page-routes count'] = K.eval('(len _page-routes)'); } catch(e) { checks['_page-routes'] = e.message; }
    try { checks['current-route'] = K.eval('(browser-location-pathname)'); } catch(e) { checks['current-route'] = e.message; }
    return checks;
  });
  console.log('  Router:', JSON.stringify(routerState));

  console.log('\n=== Done ===\n');
  await browser.close();
})();
