/**
 * sx-platform.js — Browser platform layer for the SX WASM kernel.
 *
 * Registers the 8 FFI host primitives and loads web adapter .sx files.
 * This is the only JS needed beyond the WASM kernel itself.
 *
 * Usage:
 *   <script src="sx_browser.bc.wasm.js"></script>
 *   <script src="sx-platform.js"></script>
 *
 * Or for js_of_ocaml mode:
 *   <script src="sx_browser.bc.js"></script>
 *   <script src="sx-platform.js"></script>
 */

(function() {
  "use strict";

  function boot(K) {

  // ================================================================
  // 8 FFI Host Primitives
  // ================================================================

  K.registerNative("host-global", function(args) {
    var name = args[0];
    if (typeof globalThis !== "undefined" && name in globalThis) return globalThis[name];
    if (typeof window !== "undefined" && name in window) return window[name];
    return null;
  });

  K.registerNative("host-get", function(args) {
    var obj = args[0], prop = args[1];
    if (obj == null) return null;
    var v = obj[prop];
    return v === undefined ? null : v;
  });

  K.registerNative("host-set!", function(args) {
    var obj = args[0], prop = args[1], val = args[2];
    if (obj != null) obj[prop] = val;
  });

  K.registerNative("host-call", function(args) {
    var obj = args[0], method = args[1];
    var callArgs = [];
    for (var i = 2; i < args.length; i++) callArgs.push(args[i]);
    if (obj == null) {
      // Global function call
      var fn = typeof globalThis !== "undefined" ? globalThis[method] : window[method];
      if (typeof fn === "function") return fn.apply(null, callArgs);
      return null;
    }
    if (typeof obj[method] === "function") {
      try { return obj[method].apply(obj, callArgs); }
      catch(e) { console.error("[sx] host-call error:", e); return null; }
    }
    return null;
  });

  K.registerNative("host-new", function(args) {
    var name = args[0];
    var cArgs = args.slice(1);
    var Ctor = typeof globalThis !== "undefined" ? globalThis[name] : window[name];
    if (typeof Ctor !== "function") return null;
    switch (cArgs.length) {
      case 0: return new Ctor();
      case 1: return new Ctor(cArgs[0]);
      case 2: return new Ctor(cArgs[0], cArgs[1]);
      case 3: return new Ctor(cArgs[0], cArgs[1], cArgs[2]);
      default: return new Ctor(cArgs[0], cArgs[1], cArgs[2], cArgs[3]);
    }
  });

  K.registerNative("host-callback", function(args) {
    var fn = args[0];
    // Native JS function — pass through
    if (typeof fn === "function") return fn;
    // SX callable (has __sx_handle) — wrap as JS function
    if (fn && fn.__sx_handle !== undefined) {
      return function() {
        var a = Array.prototype.slice.call(arguments);
        return K.callFn(fn, a);
      };
    }
    return function() {};
  });

  K.registerNative("host-typeof", function(args) {
    var obj = args[0];
    if (obj == null) return "nil";
    if (obj instanceof Element) return "element";
    if (obj instanceof Text) return "text";
    if (obj instanceof DocumentFragment) return "fragment";
    if (obj instanceof Document) return "document";
    if (obj instanceof Event) return "event";
    if (obj instanceof Promise) return "promise";
    if (obj instanceof AbortController) return "abort-controller";
    return typeof obj;
  });

  K.registerNative("host-await", function(args) {
    var promise = args[0], callback = args[1];
    if (promise && typeof promise.then === "function") {
      var cb;
      if (typeof callback === "function") cb = callback;
      else if (callback && callback.__sx_handle !== undefined)
        cb = function(v) { return K.callFn(callback, [v]); };
      else cb = function() {};
      promise.then(cb);
    }
  });

  // ================================================================
  // Constants expected by .sx files
  // ================================================================

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

  // ================================================================
  // DOM query helpers used by boot.sx / orchestration.sx
  // (These are JS-native in the transpiled bundle; here via FFI.)
  // ================================================================

  K.registerNative("query-sx-scripts", function(args) {
    var root = (args[0] && args[0] !== null) ? args[0] : document;
    if (typeof root.querySelectorAll !== "function") root = document;
    return Array.prototype.slice.call(root.querySelectorAll('script[type="text/sx"]'));
  });

  K.registerNative("query-page-scripts", function(args) {
    return Array.prototype.slice.call(document.querySelectorAll('script[type="text/sx-pages"]'));
  });

  K.registerNative("query-component-scripts", function(args) {
    var root = (args[0] && args[0] !== null) ? args[0] : document;
    if (typeof root.querySelectorAll !== "function") root = document;
    return Array.prototype.slice.call(root.querySelectorAll('script[type="text/sx"][data-components]'));
  });

  // localStorage
  K.registerNative("local-storage-get", function(args) {
    try { var v = localStorage.getItem(args[0]); return v === null ? null : v; }
    catch(e) { return null; }
  });
  K.registerNative("local-storage-set", function(args) {
    try { localStorage.setItem(args[0], args[1]); } catch(e) {}
  });
  K.registerNative("local-storage-remove", function(args) {
    try { localStorage.removeItem(args[0]); } catch(e) {}
  });

  // log-info/log-warn defined in browser.sx; log-error as native fallback
  K.registerNative("log-error", function(args) { console.error.apply(console, ["[sx]"].concat(args)); });

  // Cookie access (browser-side)
  K.registerNative("get-cookie", function(args) {
    var name = args[0];
    var match = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '=([^;]*)'));
    return match ? decodeURIComponent(match[1]) : null;
  });
  K.registerNative("set-cookie", function(args) {
    document.cookie = args[0] + "=" + encodeURIComponent(args[1] || "") + ";path=/;max-age=31536000;SameSite=Lax";
  });

  // IntersectionObserver — native JS to avoid bytecode callback issues
  K.registerNative("observe-intersection", function(args) {
    var el = args[0], callback = args[1], once = args[2], delay = args[3];
    var obs = new IntersectionObserver(function(entries) {
      for (var i = 0; i < entries.length; i++) {
        if (entries[i].isIntersecting) {
          var d = (delay && delay !== null) ? delay : 0;
          setTimeout(function() { K.callFn(callback, []); }, d);
          if (once) obs.unobserve(el);
        }
      }
    });
    obs.observe(el);
    return obs;
  });

  // ================================================================
  // Load SX web libraries and adapters
  // ================================================================

  // Load order follows dependency graph:
  //   1. Core spec files (parser, render, primitives already compiled into WASM kernel)
  //   2. Spec modules: signals, deps, router, page-helpers
  //   3. Bytecode compiler + VM (for JIT in browser)
  //   4. Web libraries: dom.sx, browser.sx (built on 8 FFI primitives)
  //   5. Web adapters: adapter-html, adapter-sx, adapter-dom
  //   6. Web framework: engine, orchestration, boot

  var _baseUrl = "";

  // Detect base URL and cache-bust params from current script tag.
  // _cacheBust comes from the script's own ?v= query string (used for .sx source fallback).
  // _sxbcCacheBust comes from data-sxbc-hash attribute — a separate content hash
  // covering all .sxbc files so each file gets its own correct cache buster.
  var _cacheBust = "";
  var _sxbcCacheBust = "";
  (function() {
    if (typeof document !== "undefined") {
      var scripts = document.getElementsByTagName("script");
      for (var i = scripts.length - 1; i >= 0; i--) {
        var src = scripts[i].src || "";
        if (src.indexOf("sx-platform") !== -1) {
          _baseUrl = src.substring(0, src.lastIndexOf("/") + 1);
          var qi = src.indexOf("?");
          if (qi !== -1) _cacheBust = src.substring(qi);
          var sxbcHash = scripts[i].getAttribute("data-sxbc-hash");
          if (sxbcHash) _sxbcCacheBust = "?v=" + sxbcHash;
          break;
        }
      }
    }
  })();

  /**
   * Deserialize type-tagged JSON constant back to JS value for loadModule.
   */
  function deserializeConstant(c) {
    if (!c || !c.t) return null;
    switch (c.t) {
      case 's': return c.v;
      case 'n': return c.v;
      case 'b': return c.v;
      case 'nil': return null;
      case 'sym': return { _type: 'symbol', name: c.v };
      case 'kw':  return { _type: 'keyword', name: c.v };
      case 'list': return { _type: 'list', items: (c.v || []).map(deserializeConstant) };
      case 'code': return {
        _type: 'dict',
        bytecode: { _type: 'list', items: c.v.bytecode },
        constants: { _type: 'list', items: (c.v.constants || []).map(deserializeConstant) },
        arity: c.v.arity || 0,
        'upvalue-count': c.v['upvalue-count'] || 0,
        locals: c.v.locals || 0,
      };
      case 'dict': {
        var d = { _type: 'dict' };
        for (var k in c.v) d[k] = deserializeConstant(c.v[k]);
        return d;
      }
      default: return null;
    }
  }

  /**
   * Convert a parsed SX code form ({_type:"list", items:[symbol"code", ...]})
   * into the dict format that K.loadModule / js_to_value expects.
   * Mirrors the OCaml convert_code/convert_const in sx_browser.ml.
   */
  function convertCodeForm(form) {
    if (!form || form._type !== "list" || !form.items || !form.items.length) return null;
    var items = form.items;
    if (!items[0] || items[0]._type !== "symbol" || items[0].name !== "code") return null;

    var d = { _type: "dict", arity: 0, "upvalue-count": 0 };
    for (var i = 1; i < items.length; i++) {
      var item = items[i];
      if (item && item._type === "keyword" && i + 1 < items.length) {
        var val = items[i + 1];
        if (item.name === "arity" || item.name === "upvalue-count") {
          d[item.name] = (typeof val === "number") ? val : 0;
        } else if (item.name === "bytecode" && val && val._type === "list") {
          d.bytecode = val;  // {_type:"list", items:[numbers...]}
        } else if (item.name === "constants" && val && val._type === "list") {
          d.constants = { _type: "list", items: (val.items || []).map(convertConst) };
        }
        i++; // skip value
      }
    }
    return d;
  }

  function convertConst(c) {
    if (!c || typeof c !== "object") return c; // number, string, boolean, null pass through
    if (c._type === "list" && c.items && c.items.length > 0) {
      var head = c.items[0];
      if (head && head._type === "symbol" && head.name === "code") {
        return convertCodeForm(c);
      }
      if (head && head._type === "symbol" && head.name === "list") {
        return { _type: "list", items: c.items.slice(1).map(convertConst) };
      }
    }
    return c; // symbols, keywords, etc. pass through
  }

  /**
   * Try loading a pre-compiled .sxbc bytecode module (SX text format).
   * Uses K.loadModule which handles VM suspension (import requests).
   * Returns true on success, null on failure (caller falls back to .sx source).
   */
  function loadBytecodeFile(path) {
    var sxbcPath = path.replace(/\.sx$/, '.sxbc');
    var url = _baseUrl + sxbcPath + _sxbcCacheBust;
    try {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, false);
      xhr.send();
      if (xhr.status !== 200) return null;

      // Parse the sxbc text to get the SX tree
      var parsed = K.parse(xhr.responseText);
      if (!parsed || !parsed.length) return null;
      var sxbc = parsed[0]; // (sxbc version hash (code ...))
      if (!sxbc || sxbc._type !== "list" || !sxbc.items) return null;

      // Extract the code form — 3rd or 4th item (after sxbc, version, optional hash)
      var codeForm = null;
      for (var i = 1; i < sxbc.items.length; i++) {
        var item = sxbc.items[i];
        if (item && item._type === "list" && item.items && item.items.length > 0 &&
            item.items[0] && item.items[0]._type === "symbol" && item.items[0].name === "code") {
          codeForm = item;
          break;
        }
      }
      if (!codeForm) return null;

      // Convert the SX code form to a dict for loadModule
      var moduleDict = convertCodeForm(codeForm);
      if (!moduleDict) return null;

      // Load via K.loadModule which handles VmSuspended
      var result = K.loadModule(moduleDict);

      // Handle import suspensions — fetch missing libraries on demand
      while (result && result.suspended && result.op === "import") {
        var req = result.request;
        var libName = req && req.library;
        if (libName) {
          // Try to find and load the library from the manifest
          var loaded = handleImportSuspension(libName);
          if (!loaded) {
            console.warn("[sx-platform] lazy import: library not found:", libName);
          }
        }
        // Resume the suspended module (null = library is now in env)
        result = result.resume(null);
      }

      if (typeof result === 'string' && result.indexOf('Error') === 0) {
        console.warn("[sx-platform] bytecode FAIL " + path + ":", result);
        return null;
      }
      return true;
    } catch(e) {
      console.warn("[sx-platform] bytecode FAIL " + path + ":", e.message || e);
      return null;
    }
  }

  /**
   * Handle an import suspension by finding and loading the library.
   * The library name may be an SX value (list/string) — normalize to manifest key.
   */
  function handleImportSuspension(libSpec) {
    // libSpec from the kernel is the library name spec, e.g. {_type:"list", items:[{name:"sx"},{name:"dom"}]}
    // or a string like "sx dom"
    var key;
    if (typeof libSpec === "string") {
      key = libSpec;
    } else if (libSpec && libSpec._type === "list" && libSpec.items) {
      key = libSpec.items.map(function(item) {
        return (item && item.name) ? item.name : String(item);
      }).join(" ");
    } else if (libSpec && libSpec._type === "dict") {
      // Dict with key/name fields
      key = libSpec.key || libSpec.name || "";
    } else {
      key = String(libSpec);
    }

    if (_loadedLibs[key]) return true; // already loaded

    if (!_manifest) loadManifest();
    if (!_manifest || !_manifest[key]) {
      console.warn("[sx-platform] lazy import: unknown library key '" + key + "'");
      return false;
    }

    // Load the library (and its deps) on demand
    return loadLibrary(key, {});
  }

  /**
   * Load an .sx file synchronously via XHR (boot-time only).
   * Returns the number of expressions loaded, or an error string.
   */
  function loadSxFile(path) {
    var url = _baseUrl + path + _cacheBust;
    try {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, false); // synchronous
      xhr.send();
      if (xhr.status === 200) {
        var result = K.load(xhr.responseText);
        if (typeof result === "string" && result.indexOf("Error") === 0) {
          console.error("[sx-platform] FAIL " + path + ":", result);
          return 0;
        }
        console.log("[sx-platform]  ok " + path + " (" + result + " exprs)");
        return result;
      } else {
        console.error("[sx] Failed to fetch " + path + ": HTTP " + xhr.status);
        return null;
      }
    } catch(e) {
      console.error("[sx] Failed to load " + path + ":", e);
      return null;
    }
  }

  // ================================================================
  // Manifest-driven module loader — only loads what's needed
  // ================================================================

  var _manifest = null;
  var _loadedLibs = {};

  /**
   * Fetch and parse the module manifest (library deps + file paths).
   */
  function loadManifest() {
    if (_manifest) return _manifest;
    try {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", _baseUrl + "sx/module-manifest.json" + _cacheBust, false);
      xhr.send();
      if (xhr.status === 200) {
        _manifest = JSON.parse(xhr.responseText);
        return _manifest;
      }
    } catch(e) {}
    console.warn("[sx-platform] No manifest found, falling back to full load");
    return null;
  }

  /**
   * Load a single library and all its dependencies (recursive).
   * Cycle-safe: tracks in-progress loads to break circular deps.
   * Functions in cyclic modules resolve symbols at call time via global env.
   */
  function loadLibrary(name, loading) {
    if (_loadedLibs[name]) return true;
    if (loading[name]) return true; // cycle — skip
    loading[name] = true;

    var info = _manifest[name];
    if (!info) {
      console.warn("[sx-platform] Unknown library: " + name);
      return false;
    }

    // Resolve deps first
    for (var i = 0; i < info.deps.length; i++) {
      loadLibrary(info.deps[i], loading);
    }

    // Load this module
    var ok = loadBytecodeFile("sx/" + info.file);
    if (!ok) {
      var sxFile = info.file.replace(/\.sxbc$/, '.sx');
      ok = loadSxFile("sx/" + sxFile);
    }
    _loadedLibs[name] = true;
    return !!ok;
  }

  /**
   * Load web stack using the module manifest.
   * Only downloads libraries that the entry point transitively depends on.
   */
  function loadWebStack() {
    var manifest = loadManifest();
    if (!manifest) return loadWebStackFallback();

    var entry = manifest["_entry"];
    if (!entry) {
      console.warn("[sx-platform] No _entry in manifest, falling back");
      return loadWebStackFallback();
    }

    var loading = {};
    var t0 = performance.now();
    if (K.beginModuleLoad) K.beginModuleLoad();

    // Load all entry point deps recursively
    for (var i = 0; i < entry.deps.length; i++) {
      loadLibrary(entry.deps[i], loading);
    }

    // Load entry point itself (boot.sx — not a library, just defines + init)
    loadBytecodeFile("sx/" + entry.file) || loadSxFile("sx/" + entry.file.replace(/\.sxbc$/, '.sx'));

    if (K.endModuleLoad) K.endModuleLoad();
    var count = Object.keys(_loadedLibs).length + 1; // +1 for entry
    var dt = Math.round(performance.now() - t0);
    console.log("[sx-platform] Loaded " + count + " modules in " + dt + "ms (manifest-driven)");
  }

  /**
   * Fallback: load all files in hardcoded order (pre-manifest compat).
   */
  function loadWebStackFallback() {
    var files = [
      "sx/render.sx", "sx/core-signals.sx", "sx/signals.sx", "sx/deps.sx",
      "sx/router.sx", "sx/page-helpers.sx", "sx/freeze.sx", "sx/highlight.sx",
      "sx/bytecode.sx", "sx/compiler.sx", "sx/vm.sx", "sx/dom.sx", "sx/browser.sx",
      "sx/adapter-html.sx", "sx/adapter-sx.sx", "sx/adapter-dom.sx",
      "sx/boot-helpers.sx", "sx/hypersx.sx", "sx/harness.sx",
      "sx/harness-reactive.sx", "sx/harness-web.sx",
      "sx/engine.sx", "sx/orchestration.sx", "sx/boot.sx",
    ];
    if (K.beginModuleLoad) K.beginModuleLoad();
    for (var i = 0; i < files.length; i++) {
      if (!loadBytecodeFile(files[i])) loadSxFile(files[i]);
    }
    if (K.endModuleLoad) K.endModuleLoad();
    console.log("[sx-platform] Loaded " + files.length + " files (fallback)");
  }

  /**
   * Load an optional library on demand (e.g., highlight, harness).
   * Can be called after boot for pages that need extra modules.
   */
  globalThis.__sxLoadLibrary = function(name) {
    if (!_manifest) loadManifest();
    if (!_manifest) return false;
    if (_loadedLibs[name]) return true;
    if (K.beginModuleLoad) K.beginModuleLoad();
    var ok = loadLibrary(name, {});
    if (K.endModuleLoad) K.endModuleLoad();
    return ok;
  };

  // ================================================================
  // Compatibility shim — expose Sx global matching current JS API
  // ================================================================

  globalThis.Sx = {
    VERSION: "wasm-1.0",
    parse: function(src) { return K.parse(src); },
    eval: function(src) { return K.eval(src); },
    load: function(src) { return K.load(src); },
    renderToHtml: function(expr) { return K.renderToHtml(expr); },
    callFn: function(fn, args) { return K.callFn(fn, args); },
    engine: function() { return K.engine(); },
    // Boot entry point (called by auto-init or manually)
    init: function() {
      if (typeof K.eval === "function") {
        // Check boot-init exists
        // Step through boot manually
        console.log("[sx] init-css-tracking...");
        K.eval("(init-css-tracking)");
        console.log("[sx] process-page-scripts...");
        K.eval("(process-page-scripts)");
        console.log("[sx] routes after pages:", K.eval("(len _page-routes)"));
        console.log("[sx] process-sx-scripts...");
        K.eval("(process-sx-scripts nil)");
        console.log("[sx] sx-hydrate-elements...");
        K.eval("(sx-hydrate-elements nil)");
        console.log("[sx] sx-hydrate-islands...");
        K.eval("(sx-hydrate-islands nil)");
        console.log("[sx] process-elements...");
        K.eval("(process-elements nil)");
        // Debug islands
        console.log("[sx] ~home/stepper defined?", K.eval("(type-of ~home/stepper)"));
        console.log("[sx] ~layouts/header defined?", K.eval("(type-of ~layouts/header)"));
        // Island count (JS-side, avoids VM overhead)
        console.log("[sx] manual island query:", document.querySelectorAll("[data-sx-island]").length);
        // Try hydrating again
        console.log("[sx] retry hydrate-islands...");
        K.eval("(sx-hydrate-islands nil)");
        // Check if links are boosted
        var links = document.querySelectorAll("a[href]");
        var boosted = 0;
        for (var i = 0; i < links.length; i++) {
          if (links[i]._sxBoundboost) boosted++;
        }
        console.log("[sx] boosted links:", boosted, "/", links.length);
        // Check island state
        var islands = document.querySelectorAll("[data-sx-island]");
        console.log("[sx] islands:", islands.length);
        for (var j = 0; j < islands.length; j++) {
          console.log("[sx]   island:", islands[j].getAttribute("data-sx-island"),
            "hydrated:", !!islands[j]._sxBoundislandhydrated || !!islands[j]["_sxBound" + "island-hydrated"],
            "children:", islands[j].children.length);
        }
        // Register popstate handler for back/forward navigation
        window.addEventListener("popstate", function(e) {
          var state = e.state;
          var scrollY = (state && state.scrollY) ? state.scrollY : 0;
          K.eval("(handle-popstate " + scrollY + ")");
        });
        // Signal boot complete
        document.documentElement.setAttribute("data-sx-ready", "true");
        console.log("[sx] boot done");
      }
    }
  };

  // ================================================================
  // Auto-init: load web stack and boot on DOMContentLoaded
  // ================================================================

  if (typeof document !== "undefined") {
    var _doInit = function() {
      loadWebStack();
      Sx.init();
      // Enable JIT after all boot code has run
      setTimeout(function() { K.eval('(enable-jit!)'); }, 0);
    };

    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", _doInit);
    } else {
      _doInit();
    }
  }

  } // end boot

  // SxKernel is available synchronously (js_of_ocaml) or after async
  // WASM init. Poll briefly to handle both cases.
  var K = globalThis.SxKernel;
  if (K) { boot(K); return; }
  var tries = 0;
  var poll = setInterval(function() {
    K = globalThis.SxKernel;
    if (K) { clearInterval(poll); boot(K); }
    else if (++tries > 100) { clearInterval(poll); console.error("[sx-platform] SxKernel not found after 5s"); }
  }, 50);
})();
