#!/usr/bin/env node

"use strict";

/**
 * Build script: copies hooks to dist/ for distribution.
 * For evolv-statusline.js, bundles src/statusline/*.js into a single file.
 * Run via: npm run build:hooks
 */

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

const HOOKS_DIR = path.resolve(
  __dirname,
  "..",
  "scaffold",
  "global",
  "compiled-hooks",
);
const DIST_DIR = path.join(HOOKS_DIR, "dist");
const SRC_STATUSLINE_DIR = path.join(HOOKS_DIR, "src", "statusline");

// Source hooks to distribute
const HOOKS = [
  "evolv-check-update.js",
  "evolv-statusline.js",
  "evolv-context-monitor.js",
  "evolv-session-guidance.js",
];

// Dependency order for statusline bundle (index and main are handled separately)
const STATUSLINE_MODULES = [
  "color-system",
  "helpers",
  "components",
  "grid-renderer",
  "layout-engine",
  "data-gathering",
  "lifecycle",
  "main",
];

// Built-in Node.js modules that are declared at bundle top-level
// and must be stripped from individual source modules.
const BUILTIN_MODULES = new Set([
  "fs",
  "path",
  "os",
  "crypto",
  "child_process",
]);

/**
 * Strip source-module boilerplate from a JS source file for bundling:
 * - "use strict"; line
 * - require("./...") lines for local modules (single-line and multi-line)
 * - require("builtin") lines for built-ins already declared at bundle top
 * - module.exports = { ... } blocks at end of file
 *
 * Returns the stripped content as a string.
 */
function stripModuleBoilerplate(src) {
  // Normalize CRLF to LF so the line-anchored regexes below match on
  // Windows checkouts where core.autocrlf=true rewrites JS sources.
  src = src.replace(/\r\n/g, "\n");

  // Remove "use strict"; line
  src = src.replace(/^"use strict";\n/m, "");

  // Remove single-line require of local modules:
  //   const X = require("./something");
  //   const { X } = require("./something");
  src = src.replace(/^const [^\n]+ = require\("\.\/[^"]+"\);\n/gm, "");

  // Remove single-line require of built-in modules:
  //   const fs = require("fs");
  //   const { execSync } = require("child_process");
  src = src.replace(
    /^const [^\n]+ = require\("([^"./][^"]*?)"\);\n/gm,
    (match, modName) => {
      const bare = modName.replace(/^node:/, "");
      return BUILTIN_MODULES.has(bare) ? "" : match;
    },
  );

  // Remove multi-line destructured require blocks:
  //   const {
  //     A,
  //     B,
  //   } = require("./something");
  src = removeMultilineLocalRequires(src);

  // Remove multi-line destructured require blocks for built-ins too
  src = removeMultilineBuiltinRequires(src);

  // Remove module.exports = { ... }; block at the end of the file
  src = removeModuleExports(src);

  return src;
}

/**
 * Remove multi-line `const { ... } = require("./...");` blocks.
 * These span from `const {` to `} = require("./...");`.
 */
function removeMultilineLocalRequires(src) {
  // Match: const {\n  ...\n} = require("./something");\n
  return src.replace(/^const \{[^}]*\} = require\("\.\/[^"]+"\);\n/gms, "");
}

/**
 * Remove multi-line `const { ... } = require("builtin");` blocks
 * for built-in modules that are declared at the bundle top level.
 */
function removeMultilineBuiltinRequires(src) {
  return src.replace(
    /^const \{[^}]*\} = require\("([^"./][^"]*?)"\);\n/gms,
    (match, modName) => {
      const bare = modName.replace(/^node:/, "");
      return BUILTIN_MODULES.has(bare) ? "" : match;
    },
  );
}

/**
 * Remove the trailing `module.exports = { ... };` block.
 * Finds the last occurrence of `module.exports = {` and removes to end.
 */
function removeModuleExports(src) {
  const marker = "\nmodule.exports = {";
  const idx = src.lastIndexOf(marker);
  if (idx === -1) return src;
  return src.slice(0, idx);
}

/**
 * Build the evolv-statusline.js bundle from source modules.
 * Writes the result back to the compiled-hooks directory (source of truth).
 */
function buildStatuslineBundle() {
  console.log("  [BUNDLE] evolv-statusline.js from src/statusline/");

  const bundle = buildStatuslineBundleContent();
  const origPath = path.join(HOOKS_DIR, "evolv-statusline.js");

  // Write bundle back to the source file
  fs.writeFileSync(origPath, bundle, "utf-8");
  fs.chmodSync(origPath, 0o755);
  console.log(`  [OK] evolv-statusline.js bundled (${bundle.length} bytes)`);
}

/**
 * Extract the `module.exports = { ... };` block from index.js source.
 */
function extractModuleExports(src) {
  src = src.replace(/\r\n/g, "\n");
  const marker = "module.exports = {";
  const idx = src.indexOf(marker);
  if (idx === -1) return "module.exports = {};";
  return src.slice(idx).trim();
}

/**
 * Build the evolv-statusline.js bundle content without writing to disk.
 * Returns the bundle string.
 */
function buildStatuslineBundleContent() {
  const sections = [];
  for (const mod of STATUSLINE_MODULES) {
    const filePath = path.join(SRC_STATUSLINE_DIR, `${mod}.js`);
    if (!fs.existsSync(filePath)) {
      console.error(
        `  [ERROR] src/statusline/${mod}.js — not found. Aborting.`,
      );
      process.exit(1);
    }
    const raw = fs.readFileSync(filePath, "utf-8");
    const stripped = stripModuleBoilerplate(raw);
    sections.push(
      `// ─── [${mod}] ${"─".repeat(Math.max(0, 60 - mod.length))}─`,
    );
    sections.push(stripped.trim());
    sections.push("");
  }

  const indexPath = path.join(SRC_STATUSLINE_DIR, "index.js");
  const indexSrc = fs.readFileSync(indexPath, "utf-8");
  const exportsBlock = extractModuleExports(indexSrc);

  return [
    "#!/usr/bin/env node",
    "",
    '"use strict";',
    "",
    "/**",
    " * evolv-statusline.js — StatusLine hook for Claude Code",
    " * Renders a configurable multi-row session dashboard in the terminal bottom bar.",
    " *",
    " * Layout (default 5-column grid):",
    " *   R1: brand_version (full width)",
    " *   R2: model_labeled    | protection   | project_type | status       | lifecycle",
    " *   R3: context_combined | tokens_io    | dev_rigor    | usage_time   | phase_stage",
    " *   R4: cost             | cache_hit    | style        | learning     | feature",
    " *   R5+: directory rows (project, current, added dirs) | next_cmd",
    " *",
    ' * Palette: "Warm Neutral" — 5 hues + neutral axis',
    " * NOTE: This file is generated by scripts/build-hooks.js from src/statusline/.",
    " *       Do not edit directly — edit the source modules and run npm run build:hooks.",
    " */",
    "",
    'const fs = require("fs");',
    'const path = require("path");',
    'const os = require("os");',
    'const crypto = require("crypto");',
    'const { execSync } = require("child_process");',
    "",
    sections.join("\n"),
    "",
    "// ─── Exports & Entry Point ─────────────────────────────────────────────────",
    "",
    exportsBlock,
    "",
    "if (require.main === module) { main(); }",
  ].join("\n");
}

/**
 * Verify that all compiled hooks are current (dist matches source).
 * Exits non-zero if any are stale.
 */
function verify() {
  console.log("\n  Compiled hooks verification...\n");
  let failures = 0;

  // 1. Verify statusline bundle is current
  const origPath = path.join(HOOKS_DIR, "evolv-statusline.js");
  if (!fs.existsSync(origPath)) {
    console.error(
      "  [FAIL] evolv-statusline.js does not exist. Run: npm run build:hooks\n",
    );
    process.exit(1);
  }
  const expected = buildStatuslineBundleContent();
  const existing = fs.readFileSync(origPath, "utf-8");
  if (expected !== existing) {
    console.error(
      "  [FAIL] evolv-statusline.js is stale. Run: npm run build:hooks",
    );
    failures++;
  } else {
    console.log("  [OK] evolv-statusline.js is current");
  }

  // 2. Verify dist hooks match source hooks
  for (const hook of HOOKS) {
    const srcPath = path.join(HOOKS_DIR, hook);
    const distPath = path.join(DIST_DIR, hook);

    if (!fs.existsSync(srcPath)) {
      console.error(`  [FAIL] ${hook} — source not found`);
      failures++;
      continue;
    }
    if (!fs.existsSync(distPath)) {
      console.error(
        `  [FAIL] dist/${hook} — not found. Run: npm run build:hooks`,
      );
      failures++;
      continue;
    }

    const srcContent = fs.readFileSync(srcPath, "utf-8");
    const distContent = fs.readFileSync(distPath, "utf-8");
    if (srcContent !== distContent) {
      console.error(`  [FAIL] dist/${hook} is stale. Run: npm run build:hooks`);
      failures++;
    } else {
      console.log(`  [OK] dist/${hook} is current`);
    }
  }

  if (failures > 0) {
    console.error(`\n  ${failures} hook(s) are stale.\n`);
    process.exit(1);
  }
  console.log(`\n  All ${HOOKS.length} compiled hooks are current.\n`);
}

function main() {
  if (process.argv.includes("--verify")) {
    verify();
    return;
  }

  // Ensure dist directory exists
  fs.mkdirSync(DIST_DIR, { recursive: true });

  // Build the statusline bundle first (updates the source file)
  buildStatuslineBundle();

  let copied = 0;

  for (const hook of HOOKS) {
    const src = path.join(HOOKS_DIR, hook);
    const dest = path.join(DIST_DIR, hook);

    if (!fs.existsSync(src)) {
      console.error(`  [ERROR] ${hook} — source not found. Aborting.`);
      process.exit(1);
    }

    fs.copyFileSync(src, dest);
    fs.chmodSync(dest, 0o755);
    copied++;
    console.log(`  [OK] ${hook}`);
  }

  if (copied !== HOOKS.length) {
    console.error(
      `\n  [ERROR] Only ${copied}/${HOOKS.length} hooks copied. Aborting.`,
    );
    process.exit(1);
  }
  console.log(
    `\n  Built ${copied}/${HOOKS.length} hook(s) to scaffold/global/compiled-hooks/dist/`,
  );
}

main();
