#!/usr/bin/env node
// WASM kernel integration tests: env sync, globals, pages parsing, preventDefault
const path = require('path');
const fs = require('fs');
require(path.join(__dirname, '../_build/default/browser/sx_browser.bc.js'));

const K = globalThis.SxKernel;

// Load compiler for evalVM support
const compilerFiles = ['lib/bytecode.sx', 'lib/compiler.sx', 'lib/vm.sx'];
for (const f of compilerFiles) {
  K.load(fs.readFileSync(path.join(__dirname, '../../..', f), 'utf8'));
}

let passed = 0, failed = 0;
function test(name, fn) {
  try {
    const result = fn();
    if (result === true) {
      passed++;
    } else {
      console.log(`  FAIL: ${name} — got ${JSON.stringify(result)}`);
      failed++;
    }
  } catch (e) {
    console.log(`  FAIL: ${name} — ${e.message || e}`);
    failed++;
  }
}

// ================================================================
// 1. Env binding / globals sync
// ================================================================

test('define at top level visible to VM', () => {
  K.eval('(define _test-toplevel-1 42)');
  return K.evalVM('_test-toplevel-1') === 42;
});

test('define in begin visible to VM', () => {
  K.eval('(begin (define _test-begin-1 99))');
  return K.evalVM('_test-begin-1') === 99;
});

test('set! on global syncs to VM', () => {
  K.eval('(define _test-set-g 1)');
  K.eval('(set! _test-set-g 55)');
  return K.evalVM('_test-set-g') === 55;
});

test('VM define syncs back to CEK', () => {
  K.evalVM('(define _test-vm-def 777)');
  return K.eval('_test-vm-def') === 777;
});

test('CEK and VM see same value after multiple updates', () => {
  K.eval('(define _test-ping 0)');
  K.eval('(set! _test-ping 1)');
  K.evalVM('(set! _test-ping 2)');
  const cek = K.eval('_test-ping');
  const vm = K.evalVM('_test-ping');
  return cek === 2 && vm === 2;
});

test('lambda defined at top level callable from VM', () => {
  K.eval('(define _test-top-fn (fn (x) (* x 10)))');
  return K.evalVM('(_test-top-fn 3)') === 30;
});

// ================================================================
// 2. Parse function (pages-sx format)
// ================================================================

test('parse single dict', () => {
  const r = K.eval('(get (parse "{:name \\"home\\" :path \\"/\\"}") "name")');
  return r === 'home';
});

test('parse multiple dicts returns list', () => {
  const r = K.eval('(len (parse "{:a 1}\\n{:b 2}\\n{:c 3}"))');
  return r === 3;
});

test('parse single expr unwraps', () => {
  return K.eval('(type-of (parse "42"))') === 'number';
});

test('parse multiple exprs returns list', () => {
  return K.eval('(type-of (parse "1 2 3"))') === 'list';
});

test('parse dict with content string', () => {
  const r = K.eval('(get (parse "{:name \\"test\\" :content \\"(div \\\\\\\"hello\\\\\\\")\\" :has-data false}") "content")');
  return typeof r === 'string' && r.includes('div');
});

test('parse dict with path param pattern', () => {
  const r = K.eval('(get (parse "{:path \\"/docs/<slug>\\"}") "path")');
  return r === '/docs/<slug>';
});

// ================================================================
// 3. Route pattern parsing (requires router.sx loaded)
// ================================================================

// Load router module
const routerSrc = fs.readFileSync(path.join(__dirname, '../../../web/router.sx'), 'utf8');
K.load(routerSrc);

test('parse-route-pattern splits static path', () => {
  const r = K.eval('(len (parse-route-pattern "/docs/intro"))');
  return r === 2;
});

test('parse-route-pattern detects param segments', () => {
  const r = K.eval('(get (nth (parse-route-pattern "/docs/<slug>") 1) "type")');
  return r === 'param';
});

test('parse-route-pattern detects literal segments', () => {
  const r = K.eval('(get (first (parse-route-pattern "/docs/<slug>")) "type")');
  return r === 'literal';
});

test('find-matching-route matches static path', () => {
  K.eval('(define _test-routes (list (merge {:name "home" :path "/"} {:parsed (parse-route-pattern "/")})))');
  const r = K.eval('(get (find-matching-route "/" _test-routes) "name")');
  return r === 'home';
});

test('find-matching-route matches param path', () => {
  K.eval('(define _test-routes2 (list (merge {:name "doc" :path "/docs/<slug>"} {:parsed (parse-route-pattern "/docs/<slug>")})))');
  const r = K.eval('(get (find-matching-route "/docs/intro" _test-routes2) "name")');
  return r === 'doc';
});

test('find-matching-route returns nil for no match', () => {
  return K.eval('(nil? (find-matching-route "/unknown" _test-routes))') === true;
});

// ================================================================
// 4. Click handler preventDefault pattern
// ================================================================

// Register host FFI primitives (normally done by sx-platform.js)
K.registerNative("host-global", function(args) {
  var name = args[0];
  return (typeof name === 'string') ? globalThis[name] : undefined;
});
K.registerNative("host-get", function(args) {
  var obj = args[0], key = args[1];
  if (obj == null) return null;
  var v = obj[key];
  return v === undefined ? null : v;
});
K.registerNative("host-call", function(args) {
  var obj = args[0], method = args[1];
  var callArgs = args.slice(2);
  if (obj == null || typeof obj[method] !== 'function') return null;
  try { return obj[method].apply(obj, callArgs); } catch(e) { return null; }
});
K.registerNative("host-set!", function(args) {
  var obj = args[0], key = args[1], val = args[2];
  if (obj != null) obj[key] = val;
  return null;
});

test('host-call preventDefault on mock event', () => {
  let prevented = false;
  globalThis._testMockEvent = {
    preventDefault: () => { prevented = true; },
    type: 'click',
    target: { tagName: 'A', getAttribute: () => '/test' }
  };
  K.eval('(host-call (host-global "_testMockEvent") "preventDefault")');
  delete globalThis._testMockEvent;
  return prevented === true;
});

test('host-get reads property from JS object', () => {
  globalThis._testObj = { foo: 42 };
  const r = K.eval('(host-get (host-global "_testObj") "foo")');
  delete globalThis._testObj;
  return r === 42;
});

test('host-set! writes property on JS object', () => {
  globalThis._testObj2 = { val: 0 };
  K.eval('(host-set! (host-global "_testObj2") "val" 99)');
  const r = globalThis._testObj2.val;
  delete globalThis._testObj2;
  return r === 99;
});

test('click handler pattern: check target, prevent, navigate', () => {
  let prevented = false;
  let navigated = null;
  globalThis._testClickEvent = {
    preventDefault: () => { prevented = true; },
    type: 'click',
    target: { tagName: 'A', href: '/about' }
  };
  globalThis._testNavigate = (url) => { navigated = url; };

  K.eval(`
    (let ((e (host-global "_testClickEvent")))
      (let ((tag (host-get (host-get e "target") "tagName")))
        (when (= tag "A")
          (host-call e "preventDefault")
          (host-call (host-global "_testNavigate") "call" nil
            (host-get (host-get e "target") "href")))))
  `);

  delete globalThis._testClickEvent;
  delete globalThis._testNavigate;
  return prevented === true && navigated === '/about';
});

// ================================================================
// 5. Iterative cek_run — deep evaluation without stack overflow
// ================================================================

test('deep recursion via foldl (100 iterations)', () => {
  const r = K.eval('(reduce + 0 (map (fn (x) x) (list ' +
    Array.from({length: 100}, (_, i) => i + 1).join(' ') + ')))');
  return r === 5050;
});

test('deeply nested let bindings', () => {
  // Build (let ((x0 0)) (let ((x1 (+ x0 1))) ... (let ((xN (+ xN-1 1))) xN)))
  let expr = 'x49';
  for (let i = 49; i >= 0; i--) {
    const prev = i === 0 ? '0' : `(+ x${i-1} 1)`;
    expr = `(let ((x${i} ${prev})) ${expr})`;
  }
  return K.eval(expr) === 49;
});

// ================================================================
// Results
// ================================================================

console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
