#!/usr/bin/env node

/**
 * Bundle dist/ into a single file using esbuild.
 *
 * Strategy:
 * 1. tsc compiles TS → JS (no source maps, no declarations)
 * 2. esbuild bundles + minifies all JS into dist/index.cjs
 *    - All variable names shortened
 *    - All whitespace removed
 *    - Dead code eliminated
 *    - No directory structure visible
 *    - No file names visible
 *
 * esbuild minification alone provides strong protection:
 * - Variables become single-letter names (a, b, c...)
 * - All comments and formatting stripped
 * - Original module structure completely flattened
 * - No source maps, no type declarations
 */

import * as esbuild from 'esbuild';
import { readFileSync, writeFileSync, readdirSync, rmSync, statSync, copyFileSync, mkdirSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';

const DIST_DIR = join(process.cwd(), 'dist');
const ENTRY = join(DIST_DIR, 'index.js');
const BUNDLE_OUTPUT = join(DIST_DIR, 'index.bundled.js');

// Step 1: Bundle with esbuild
console.log('[bundle] Bundling with esbuild...');

try {
  const result = await esbuild.build({
    entryPoints: [ENTRY],
    bundle: true,
    platform: 'node',
    target: 'node20',
    format: 'cjs',
    outfile: BUNDLE_OUTPUT,
    // Replace import.meta.url with CJS equivalent
    define: {
      'import.meta.url': '__dirname_url__',
    },
    // Inject __dirname_url__ variable
    inject: [join(process.cwd(), 'scripts', 'import-meta-shim.js')],
    external: [
      'sharp',
      'playwright',
      '@xenova/transformers',
      'tiktoken',
      'yoga-layout',
      // Heavy dependencies externalized for faster startup
      'pino-pretty',
      'docx',
      'exceljs',
      'mammoth',
      'pptxgenjs',
      'openai',
      // bindings is only used by better-sqlite3's fallback path (when nativeBinding is not provided)
      // We always provide nativeBinding, so bindings is never actually called at runtime
      'bindings',
    ],
    minify: true,
    legalComments: 'none',
    treeShaking: true,
    metafile: true,
  });

  if (result.metafile) {
    console.log(`[bundle] Input: ${Object.keys(result.metafile.inputs).length} modules`);
  }
  const outputSize = statSync(BUNDLE_OUTPUT).size;
  console.log(`[bundle] Output: ${(outputSize / 1024).toFixed(0)} KB`);
} catch (error) {
  console.error('[bundle] Failed:', error.message);
  process.exit(1);
}

// Step 1.5: Copy WASM files to dist/ (esbuild cannot bundle binary .wasm files)
const WASM_FILES = [
  { src: 'web-tree-sitter/web-tree-sitter.wasm', dest: 'web-tree-sitter.wasm' },
  { src: 'tree-sitter-cpp/tree-sitter-cpp.wasm', dest: 'tree-sitter-cpp.wasm' },
  { src: 'tree-sitter-c/tree-sitter-c.wasm', dest: 'tree-sitter-c.wasm' },
];

for (const { src, dest } of WASM_FILES) {
  const srcPath = join(process.cwd(), 'node_modules', src);
  const destPath = join(DIST_DIR, dest);
  if (existsSync(srcPath)) {
    copyFileSync(srcPath, destPath);
    console.log(`[bundle] Copied WASM: ${dest}`);
  } else {
    console.warn(`[bundle] WASM not found: ${src}`);
  }
}

// Step 1.6: Copy pre-compiled native modules to dist/native/
// This avoids requiring users to have C++ build tools installed
const NATIVE_DIR = join(DIST_DIR, 'native');
const NATIVE_MODULES = [
  { src: 'better-sqlite3/build/Release/better_sqlite3.node', dest: 'better_sqlite3.node' },
];

if (!existsSync(NATIVE_DIR)) {
  mkdirSync(NATIVE_DIR, { recursive: true });
}

for (const { src, dest } of NATIVE_MODULES) {
  const srcPath = join(process.cwd(), 'node_modules', src);
  const destPath = join(NATIVE_DIR, dest);
  if (existsSync(srcPath)) {
    try {
      copyFileSync(srcPath, destPath);
      console.log(`[bundle] Copied native: ${dest}`);
    } catch (err) {
      console.warn(`[bundle] Failed to copy native module ${dest}: ${err.code} — using existing file if present`);
    }
  } else {
    console.warn(`[bundle] Native module not found: ${src} — users will need build tools`);
  }
}

// Step 2: Remove original directory structure
console.log('[bundle] Cleaning up original files...');

const filesToKeep = new Set(['index.bundled.js', 'web-tree-sitter.wasm', 'tree-sitter-cpp.wasm', 'tree-sitter-c.wasm', 'cli.cjs']);
const dirsToKeep = new Set(['native']);

function cleanDir(dir) {
  const entries = readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    if (entry.isDirectory()) {
      if (!dirsToKeep.has(entry.name)) {
        rmSync(fullPath, { recursive: true, force: true });
      }
    } else if (!filesToKeep.has(entry.name)) {
      rmSync(fullPath, { force: true });
    }
  }
}
cleanDir(DIST_DIR);

// Step 3: Fix shebang and write final file
// Remove shebang from bundle body (it gets displaced by processing)
let bundleContent = readFileSync(BUNDLE_OUTPUT, 'utf-8');
if (bundleContent.startsWith('#!')) {
  bundleContent = bundleContent.substring(bundleContent.indexOf('\n') + 1);
}

const finalPath = join(DIST_DIR, 'index.cjs');
writeFileSync(finalPath, '#!/usr/bin/env node\n' + bundleContent, 'utf-8');
rmSync(BUNDLE_OUTPUT, { force: true });

const finalSize = statSync(finalPath).size;
console.log(`[bundle] Done: dist/index.cjs (${(finalSize / 1024).toFixed(0)} KB)`);
console.log('[bundle] Complete — single minified file, no source exposure');

// Step 4: Generate CLI wrapper that sets --max-old-space-size=4096
// The shebang #!/usr/bin/env node cannot pass flags, so we need a wrapper
// that re-spawns node with the larger heap limit when needed.
// Use .cjs extension so Node treats it as CommonJS regardless of package.json "type"
const cliWrapperPath = join(DIST_DIR, 'cli.cjs');
const cliWrapperContent = `#!/usr/bin/env node
// AggRoot CLI wrapper - ensures Node.js heap is large enough for long sessions.
const v8 = require('node:v8');
const path = require('node:path');
const { spawn } = require('node:child_process');
const heapLimitMB = Math.round(v8.getHeapStatistics().heap_size_limit / 1024 / 1024);

if (heapLimitMB < 3500 && !process.env.AGGROOT_SPAWNED) {
  // Re-spawn with larger heap, inheriting stdio for seamless UX
  const child = spawn(process.execPath, [
    '--max-old-space-size=4096',
    '--no-warnings',
    path.resolve(__dirname, 'index.cjs'),
    ...process.argv.slice(2),
  ], {
    stdio: 'inherit',
    env: { ...process.env, AGGROOT_SPAWNED: '1' },
  });
  child.on('error', (err) => {
    console.error('Failed to re-spawn with larger heap:', err.message);
    process.exit(1);
  });
  child.on('exit', (code) => process.exit(code ?? 0));
} else {
  require('./index.cjs');
}
`;
writeFileSync(cliWrapperPath, cliWrapperContent, 'utf-8');
console.log(`[bundle] CLI wrapper: dist/cli.cjs`);
