#!/usr/bin/env node
// test_wasm_native.js — Run WASM kernel tests in Node.js using the actual
// WASM binary (not js_of_ocaml JS fallback). Tests are SX deftest forms
// in web/tests/test-wasm-browser.sx.
//
// Usage: node hosts/ocaml/browser/test_wasm_native.js
//        SX_TEST_BYTECODE=1 node hosts/ocaml/browser/test_wasm_native.js

const fs = require('fs');
const path = require('path');

const PROJECT_ROOT = path.resolve(__dirname, '../../..');
const WASM_DIR = path.join(PROJECT_ROOT, 'shared/static/wasm');

// --- DOM stubs ---
function makeElement(tag) {
  const el = {
    tagName: tag, _attrs: {}, _children: [], style: {},
    childNodes: [], children: [], textContent: '',
    nodeType: 1,
    setAttribute(k, v) { el._attrs[k] = String(v); },
    getAttribute(k) { return el._attrs[k] || null; },
    removeAttribute(k) { delete el._attrs[k]; },
    appendChild(c) { el._children.push(c); el.childNodes.push(c); el.children.push(c); return c; },
    insertBefore(c) { el._children.push(c); el.childNodes.push(c); el.children.push(c); return c; },
    removeChild(c) { return c; },
    replaceChild(n) { return n; },
    cloneNode() { return makeElement(tag); },
    addEventListener() {}, removeEventListener() {}, dispatchEvent() {},
    get innerHTML() {
      return el._children.map(c => {
        if (c._isText) return c.textContent || '';
        if (c._isComment) return '<!--' + (c.textContent || '') + '-->';
        return c.outerHTML || '';
      }).join('');
    },
    set innerHTML(v) { el._children = []; el.childNodes = []; el.children = []; },
    get outerHTML() {
      let s = '<' + tag;
      for (const k of Object.keys(el._attrs).sort()) s += ` ${k}="${el._attrs[k]}"`;
      s += '>';
      if (['br','hr','img','input','meta','link'].includes(tag)) return s;
      return s + el.innerHTML + '</' + tag + '>';
    },
    dataset: new Proxy({}, {
      get(_, k) { return el._attrs['data-' + k.replace(/[A-Z]/g, c => '-' + c.toLowerCase())]; },
      set(_, k, v) { el._attrs['data-' + k.replace(/[A-Z]/g, c => '-' + c.toLowerCase())] = v; return true; }
    }),
    querySelectorAll() { return []; },
    querySelector() { return null; },
  };
  return el;
}

global.window = global;
global.document = {
  createElement: makeElement,
  createDocumentFragment() { return makeElement('fragment'); },
  head: makeElement('head'), body: makeElement('body'),
  querySelector() { return null; }, querySelectorAll() { return []; },
  createTextNode(s) { return { _isText: true, textContent: String(s), nodeType: 3 }; },
  addEventListener() {},
  createComment(s) { return { _isComment: true, textContent: s || '', nodeType: 8 }; },
  getElementsByTagName() { return []; },
};
global.localStorage = { getItem() { return null; }, setItem() {}, removeItem() {} };
global.CustomEvent = class { constructor(n, o) { this.type = n; this.detail = (o || {}).detail || {}; } };
global.MutationObserver = class { observe() {} disconnect() {} };
global.requestIdleCallback = fn => setTimeout(fn, 0);
global.matchMedia = () => ({ matches: false });
global.navigator = { serviceWorker: { register() { return Promise.resolve(); } } };
global.location = { href: '', pathname: '/', hostname: 'localhost' };
global.history = { pushState() {}, replaceState() {} };
global.fetch = () => Promise.resolve({ ok: true, text() { return Promise.resolve(''); } });
global.XMLHttpRequest = class { open() {} send() {} };

// --- Load WASM kernel ---
async function main() {
  // The WASM loader sets globalThis.SxKernel after async init
  require(path.join(WASM_DIR, 'sx_browser.bc.wasm.js'));

  // Poll for SxKernel (WASM init is async)
  const K = await new Promise((resolve, reject) => {
    let tries = 0;
    const poll = setInterval(() => {
      if (globalThis.SxKernel) { clearInterval(poll); resolve(globalThis.SxKernel); }
      else if (++tries > 200) { clearInterval(poll); reject(new Error('SxKernel not found after 10s')); }
    }, 50);
  });

  console.log('WASM kernel loaded (native WASM, not JS fallback)');

  // --- Register 8 FFI host primitives ---
  K.registerNative('host-global', args => {
    const name = args[0];
    return (name in globalThis) ? globalThis[name] : null;
  });
  K.registerNative('host-get', args => {
    const [obj, prop] = args;
    if (obj == null) return null;
    const v = obj[prop];
    return v === undefined ? null : v;
  });
  K.registerNative('host-set!', args => { if (args[0] != null) args[0][args[1]] = args[2]; return args[2]; });
  K.registerNative('host-call', args => {
    const [obj, method, ...rest] = args;
    if (obj == null || typeof obj[method] !== 'function') return null;
    const r = obj[method].apply(obj, rest);
    return r === undefined ? null : r;
  });
  K.registerNative('host-new', args => new (Function.prototype.bind.apply(args[0], [null, ...args.slice(1)])));
  K.registerNative('host-callback', args => function() { return K.callFn(args[0], Array.from(arguments)); });
  K.registerNative('host-typeof', args => typeof args[0]);
  K.registerNative('host-await', args => args[0]);

  K.eval('(define SX_VERSION "test-wasm-1.0")');
  K.eval('(define SX_ENGINE "ocaml-vm-wasm-test")');
  K.eval('(define parse sx-parse)');
  K.eval('(define serialize sx-serialize)');

  // --- Load web stack modules ---
  const useBytecode = process.env.SX_TEST_BYTECODE === '1';
  const sxDir = path.join(WASM_DIR, 'sx');
  const modules = [
    'render', 'core-signals', 'signals', 'deps', 'router', 'page-helpers', 'freeze',
    'bytecode', 'compiler', 'vm', 'dom', 'browser',
    'adapter-html', 'adapter-sx', 'adapter-dom',
    'boot-helpers', 'hypersx',
    'harness', 'harness-reactive', 'harness-web',
    'engine', 'orchestration', 'boot',
  ];

  if (K.beginModuleLoad) K.beginModuleLoad();
  for (const mod of modules) {
    let loaded = false;
    if (useBytecode) {
      try {
        const bcSrc = fs.readFileSync(path.join(sxDir, mod + '.sxbc'), 'utf8');
        global.__sxbcText = bcSrc;
        const r = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
        delete global.__sxbcText;
        if (typeof r !== 'string' || !r.startsWith('Error')) { loaded = true; }
      } catch (e) { delete global.__sxbcText; }
    }
    if (!loaded) {
      const src = fs.readFileSync(path.join(sxDir, mod + '.sx'), 'utf8');
      K.load(src);
    }
  }
  if (K.endModuleLoad) K.endModuleLoad();

  // --- Register test framework hooks ---
  let pass = 0, fail = 0;
  const suiteStack = [];

  K.registerNative('report-pass', args => {
    pass++;
    return null;
  });
  K.registerNative('report-fail', args => {
    fail++;
    const suitePath = suiteStack.join(' > ');
    console.error(`FAIL: ${suitePath ? suitePath + ' > ' : ''}${args[0]}\n  ${args[1]}`);
    return null;
  });
  K.registerNative('push-suite', args => {
    suiteStack.push(args[0]);
    return null;
  });
  K.registerNative('pop-suite', args => {
    suiteStack.pop();
    return null;
  });
  // try-call must return {"ok": bool, "error": string|nil} for the test framework
  K.eval('(define try-call (fn (thunk) (let ((result (cek-try thunk (fn (err) err)))) (if (and (= (type-of result) "string") (starts-with? result "Error")) {"ok" false "error" result} {"ok" true "error" nil}))))');


  // --- Load test framework + SX test file ---
  K.load(fs.readFileSync(path.join(PROJECT_ROOT, 'spec/tests/test-framework.sx'), 'utf8'));
  K.load(fs.readFileSync(path.join(PROJECT_ROOT, 'web/tests/test-wasm-browser.sx'), 'utf8'));

  // --- Summary ---
  console.log(`WASM native tests: ${pass} passed, ${fail} failed`);
  process.exit(fail > 0 ? 1 : 0);
}

main().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
