#!/usr/bin/env node
/**
 * wrap-modules.js — Add define-library wrappers and import declarations
 * to browser .sx SOURCE files for lazy loading support.
 *
 * Targets the real source locations (spec/, web/, lib/), NOT dist/.
 * Run bundle.sh after to copy to dist/, then compile-modules.js.
 *
 * - 8 unwrapped files get define-library + export + begin wrappers
 * - 4 already-wrapped files get dependency import declarations
 * - boot.sx gets imports (stays unwrapped — entry point)
 */

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

const ROOT = path.resolve(__dirname, '..', '..', '..');

// Source file → library name (null = entry point)
const MODULES = {
  // Spec modules
  'spec/render.sx':           { lib: '(sx render)',           deps: [] },
  'spec/signals.sx':          { lib: '(sx signals)',          deps: [] },
  'web/web-signals.sx':       { lib: '(sx signals-web)',      deps: ['(sx dom)', '(sx browser)'] },
  'web/deps.sx':              { lib: '(web deps)',            deps: [] },
  'web/router.sx':            { lib: '(web router)',          deps: [] },
  'web/page-helpers.sx':      { lib: '(web page-helpers)',    deps: [] },
  // Lib modules
  'lib/freeze.sx':            { lib: '(sx freeze)',           deps: [] },
  'lib/highlight.sx':         { lib: '(sx highlight)',        deps: [] },
  'lib/bytecode.sx':          { lib: '(sx bytecode)',         deps: [] },
  'lib/compiler.sx':          { lib: '(sx compiler)',         deps: [] },
  'lib/vm.sx':                { lib: '(sx vm)',               deps: [] },
  // Web FFI
  'web/lib/dom.sx':           { lib: '(sx dom)',              deps: [] },
  'web/lib/browser.sx':       { lib: '(sx browser)',          deps: [] },
  // Web adapters
  'web/adapter-html.sx':      { lib: '(web adapter-html)',    deps: ['(sx render)'] },
  'web/adapter-sx.sx':        { lib: '(web adapter-sx)',      deps: ['(web boot-helpers)'] },
  'web/adapter-dom.sx':       { lib: '(web adapter-dom)',     deps: ['(sx dom)', '(sx render)'] },
  // Web framework
  'web/lib/boot-helpers.sx':  { lib: '(web boot-helpers)',    deps: ['(sx dom)', '(sx browser)', '(web adapter-dom)'] },
  'web/lib/hypersx.sx':       { lib: '(sx hypersx)',          deps: [] },
  'web/engine.sx':            { lib: '(web engine)',          deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)'] },
  'web/orchestration.sx':     { lib: '(web orchestration)',   deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)', '(web adapter-dom)', '(web engine)'] },
  'web/boot.sx':              { lib: null,                    deps: ['(sx dom)', '(sx browser)', '(web boot-helpers)', '(web adapter-dom)',
                                                                      '(sx signals)', '(sx signals-web)', '(web router)', '(web page-helpers)',
                                                                      '(web orchestration)', '(sx render)',
                                                                      '(sx bytecode)', '(sx compiler)', '(sx vm)'] },
  // Test harness
  'spec/harness.sx':          { lib: '(sx harness)',          deps: [] },
  'web/harness-reactive.sx':  { lib: '(sx harness-reactive)', deps: [] },
  'web/harness-web.sx':       { lib: '(sx harness-web)',      deps: [] },
};

// Extract top-level define names from source.
// Handles both `(define name ...)` and `(define\n  name ...)` formats.
function extractDefineNames(source) {
  const names = [];
  const lines = source.split('\n');
  let depth = 0;
  let expectName = false;
  for (const line of lines) {
    if (depth === 0) {
      const m = line.match(/^\(define\s+\(?(\S+)/);
      if (m) {
        names.push(m[1]);
        expectName = false;
      } else if (line.match(/^\(define\s*$/)) {
        expectName = true;
      }
    } else if (depth === 1 && expectName) {
      const m = line.match(/^\s+(\S+)/);
      if (m) {
        names.push(m[1]);
        expectName = false;
      }
    }
    for (const ch of line) {
      if (ch === '(') depth++;
      else if (ch === ')') depth--;
    }
  }
  return names;
}

function processFile(relPath, info) {
  const filePath = path.join(ROOT, relPath);
  if (!fs.existsSync(filePath)) {
    console.log('  SKIP', relPath, '(not found)');
    return;
  }

  let source = fs.readFileSync(filePath, 'utf8');
  const { lib, deps } = info;
  const hasWrapper = source.includes('(define-library');
  const hasDepImports = deps.length > 0 && source.match(/^\(import\s+\(/m) &&
    !source.match(/^\(import\s+\(\w+ \w+\)\)\s*$/m); // more than just self-import

  // Skip files with no deps and already wrapped (or no wrapper needed)
  if (deps.length === 0 && (hasWrapper || !lib)) {
    console.log('  ok', relPath, '(no changes needed)');
    return;
  }

  // Build import lines for deps
  const importLines = deps.map(d => `(import ${d})`).join('\n');

  // CASE 1: Entry point (boot.sx) — just add imports at top
  if (!lib) {
    if (deps.length > 0 && !source.startsWith('(import')) {
      source = importLines + '\n\n' + source;
      fs.writeFileSync(filePath, source);
      console.log('  +imports', relPath, `(${deps.length} deps, entry point)`);
    } else {
      console.log('  ok', relPath, '(entry point, already has imports)');
    }
    return;
  }

  // CASE 2: Already wrapped — add imports before define-library
  if (hasWrapper) {
    if (deps.length > 0) {
      // Check if imports already present
      const firstImportCheck = deps[0].replace(/[()]/g, '\\$&');
      if (source.match(new RegExp('\\(import ' + firstImportCheck))) {
        console.log('  ok', relPath, '(already has dep imports)');
        return;
      }
      const dlIdx = source.indexOf('(define-library');
      source = source.slice(0, dlIdx) + importLines + '\n\n' + source.slice(dlIdx);
      fs.writeFileSync(filePath, source);
      console.log('  +imports', relPath, `(${deps.length} deps)`);
    }
    return;
  }

  // CASE 3: Needs full wrapping
  if (deps.length === 0 && !hasWrapper) {
    // Wrap with no deps
    const names = extractDefineNames(source);
    if (names.length === 0) {
      console.log('  WARN', relPath, '— no defines found, skipping');
      return;
    }
    const wrapped = buildWrapped(lib, names, source, '');
    fs.writeFileSync(filePath, wrapped);
    console.log('  wrapped', relPath, `as ${lib} (${names.length} exports)`);
    return;
  }

  // Wrap with deps
  const names = extractDefineNames(source);
  if (names.length === 0) {
    console.log('  WARN', relPath, '— no defines found, skipping');
    return;
  }
  const wrapped = buildWrapped(lib, names, source, importLines);
  fs.writeFileSync(filePath, wrapped);
  console.log('  wrapped', relPath, `as ${lib} (${names.length} exports, ${deps.length} deps)`);
}

function buildWrapped(libName, exportNames, bodySource, importSection) {
  const parts = [];

  // Dependency imports (top-level, before define-library)
  if (importSection) {
    parts.push(importSection);
    parts.push('');
  }

  // define-library header
  parts.push(`(define-library ${libName}`);
  parts.push(`  (export ${exportNames.join(' ')})`);
  parts.push('  (begin');
  parts.push('');

  // Body (original source, indented)
  parts.push(bodySource);
  parts.push('');

  // Close begin + define-library
  parts.push('))');
  parts.push('');

  // Self-import for backward compat
  parts.push(`;; Re-export to global env`);
  parts.push(`(import ${libName})`);
  parts.push('');

  return parts.join('\n');
}

console.log('Processing source .sx files...\n');
for (const [relPath, info] of Object.entries(MODULES)) {
  processFile(relPath, info);
}
console.log('\nDone! Now run:');
console.log('  bash hosts/ocaml/browser/bundle.sh');
console.log('  node hosts/ocaml/browser/compile-modules.js');
