// MBTX Route Handler
//
// Executes pre-built .mbtx JS artifacts in-process via dynamic import().
// Build step transforms the IIFE output into an ESM module with render() export.
// The render function reads __MBTX_ENV__ and returns __MBTX_RESPONSE__.

///|
/// Configuration for a .mbtx route
pub(all) struct MbtxRoute {
  /// URL pattern (e.g., "/hello", "/api/:id")
  path : String
  /// Path to the built ESM JS artifact (.mjs)
  js_path : String
}

///|
/// Register .mbtx routes to the app.
/// Each route calls the pre-loaded render() function from the ESM module.
pub fn register_mbtx_routes(
  app : App,
  routes : Array[MbtxRoute],
) -> Unit {
  for route in routes {
    let js_path = route.js_path
    app.get(
      route.path,
      @mars.handler(async fn(c) {
        let req_path = c.param("path").unwrap_or(route.path)
        let query = ffi_get_query_string(c)
        let result = ffi_exec_mbtx_esm(js_path, "GET", req_path, query)
        if result.status == 0 {
          ffi_console_error(
            "[sol] mbtx handler failed (\{js_path}): execution error",
          )
          c.text("Internal Server Error", status=500)
          return
        }
        c.set_header("Content-Type", result.content_type)
        c.text(result.body, status=result.status)
      }),
    )
  }
}

///|
/// Result from mbtx execution
struct MbtxResponse {
  status : Int
  content_type : String
  body : String
}

///|
/// Execute a built .mbtx ESM module via dynamic import() with hot-reload.
/// Modules are cached and invalidated when file mtime changes.
/// Uses import() for V8 JIT optimization (~0.4µs/call vs ~150µs with vm).
extern "js" fn ffi_exec_mbtx_esm(
  js_path : String,
  req_method : String,
  path_info : String,
  query_string : String,
) -> MbtxResponse =
  #| (() => {
  #|   const fs = require('node:fs');
  #|   const path = require('node:path');
  #|   const cache = new Map();
  #|   return (jsPath, method, pathInfo, queryString) => {
  #|     try {
  #|       const abs = path.resolve(jsPath);
  #|       const mtime = fs.statSync(abs).mtimeMs;
  #|       let entry = cache.get(abs);
  #|       if (!entry || entry.mtime !== mtime) {
  #|         const url = 'file://' + abs + '?t=' + mtime;
  #|         entry = { mtime, mod: null, loading: import(url).then(m => { entry.mod = m; }) };
  #|         cache.set(abs, entry);
  #|       }
  #|       if (!entry.mod) {
  #|         return { status: 0, content_type: '', body: '' };
  #|       }
  #|       const env = { REQUEST_METHOD: method, PATH_INFO: pathInfo, QUERY_STRING: queryString };
  #|       const resp = entry.mod.default(env);
  #|       if (!resp) return { status: 0, content_type: '', body: '' };
  #|       return {
  #|         status: resp.status || 200,
  #|         content_type: resp.contentType || 'text/html',
  #|         body: resp.body || '',
  #|       };
  #|     } catch (e) {
  #|       console.error('[sol] mbtx execution error:', e);
  #|       return { status: 0, content_type: '', body: '' };
  #|     }
  #|   };
  #| })()

///|
extern "js" fn ffi_get_query_string(c : Ctx) -> String =
  #| (c) => {
  #|   try {
  #|     const url = new URL(c.req.url, 'http://localhost');
  #|     return url.search ? url.search.slice(1) : '';
  #|   } catch (_) { return ''; }
  #| }

// =============================================================================
// Build Utilities
// =============================================================================

///|
/// Transform a built .mbtx JS (IIFE) into an ESM module with render() export.
/// Input: standard moonc output with (() => { ... })() at the end
/// Output: same code but IIFE replaced with `export default function render(env) { ... }`
pub fn transform_mbtx_to_esm(code : String) -> String {
  // Find the IIFE: (() => {
  let iife_start = match code.find("(() => {") {
    Some(i) => i
    None => return code // not a standard IIFE, return as-is
  }
  let buf = StringBuilder::new()
  // Copy helper definitions before IIFE
  buf.write_string(code.substring(end=iife_start))
  // Replace IIFE with export default function
  buf.write_string(
    "export default function render(env) { globalThis.__MBTX_ENV__ = env; globalThis.__MBTX_RESPONSE__ = undefined;",
  )
  // Find the closing })();
  let iife_body_start = iife_start + 8 // skip "(() => {"
  // Find matching })();
  let close_pattern = "})();"
  let close_idx = match code.find(close_pattern) {
    Some(i) => i
    None => {
      // Try without semicolon
      match code.find("})()") {
        Some(i) => i
        None => return code
      }
    }
  }
  // Copy IIFE body
  buf.write_string(code.substring(start=iife_body_start, end=close_idx))
  buf.write_string(" return globalThis.__MBTX_RESPONSE__; }\n")
  buf.to_string()
}