#!/usr/bin/env node
// AEGIS SOAR — rebuild-and-inverse-verify (AS/PLATFORM/2026/002 §4, §5.2, §5.4)
//
// The stale-dist guard + minification-signature method, encoded once so M3/M4
// instantiate it instead of re-deriving it.
//
//   1. (default) remove stale `dist` and `npm run build` — so the bundle under
//      test is provably FRESH, not a stale artefact from a prior build.
//   2. grep the fresh bundle for STRING-LITERAL markers from a manifest:
//        - positive markers MUST be present (count >= min)  — the fix is in the bundle.
//        - negative markers MUST be absent  (count == 0)    — e.g. `to:"DISPOSITIONED"`
//          proving an M3-fenced edge did NOT leak into the M2 bundle.
//      Only string literals are matched (route paths, scope/state strings, event
//      types, SQL identifiers) — NEVER minified function/const names, which esbuild
//      mangles (§5.4). The bare token of an enum value (e.g. DISPOSITIONED) is a
//      legitimate at-rest string and is NOT a valid negative marker; use the precise
//      transition-target literal instead.
//
// Usage:
//   node tools/rebuild-inverse-verify.cjs [--markers <file>] [--bundle <file>] [--no-build]
//
//   --markers <file>  marker manifest (default tools/markers/m2-fincrime-bundle-markers.json)
//   --bundle <file>   bundle to grep (default: the manifest's "bundle", else dist/index.cjs)
//   --no-build        skip the rebuild; grep the existing bundle as-is (verify-by-use
//                     on an already-fresh bundle; the rebuild path is the default)
//
// Exit codes: 0 = all positives present AND all negatives absent; 1 = any marker
// fails; 2 = usage / file / build error.

const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");

function parseArgs(argv) {
  const args = {};
  for (let i = 2; i < argv.length; i++) {
    const a = argv[i];
    if (a === "--help" || a === "-h") return null;
    if (a === "--no-build") { args.noBuild = true; continue; }
    if (a.startsWith("--")) { args[a.slice(2)] = argv[i + 1]; i++; }
  }
  return args;
}

function usage() {
  process.stderr.write(
    "AEGIS SOAR rebuild-and-inverse-verify\n\n" +
    "Usage:\n" +
    "  node tools/rebuild-inverse-verify.cjs [--markers <file>] [--bundle <file>] [--no-build]\n\n" +
    "Exit codes: 0=all markers satisfied, 1=marker failure, 2=usage/file/build error\n",
  );
  process.exit(2);
}

function fail(msg) { process.stderr.write(`ERROR: ${msg}\n`); process.exit(2); }

// count NON-OVERLAPPING occurrences of a literal in text (string-literal grep)
function countLiteral(haystack, needle) {
  if (needle.length === 0) return 0;
  let n = 0, i = 0;
  for (;;) {
    const j = haystack.indexOf(needle, i);
    if (j === -1) break;
    n++;
    i = j + needle.length;
  }
  return n;
}

function main() {
  const args = parseArgs(process.argv);
  if (!args) usage();

  const root = process.cwd();
  const markersPath = path.resolve(root, args.markers || "tools/markers/m2-fincrime-bundle-markers.json");
  let manifest;
  try { manifest = JSON.parse(fs.readFileSync(markersPath, "utf8")); }
  catch (e) { fail(`cannot read markers manifest ${markersPath}: ${e.message}`); }

  const bundlePath = path.resolve(root, args.bundle || manifest.bundle || "dist/index.cjs");

  if (!args.noBuild) {
    process.stdout.write(">>> rebuild: removing stale dist + npm run build (stale-dist guard) …\n");
    try {
      // `npm run build` itself rm's dist; this guarantees the bundle under test is fresh.
      execSync("npm run build", { cwd: root, stdio: "inherit" });
    } catch (e) {
      fail(`build failed: ${e.message}`);
    }
  } else {
    process.stdout.write(">>> --no-build: grepping existing bundle as-is (already-fresh)\n");
  }

  let bundle;
  try { bundle = fs.readFileSync(bundlePath, "utf8"); }
  catch (e) { fail(`cannot read bundle ${bundlePath}: ${e.message}`); }

  process.stdout.write("=".repeat(82) + "\n");
  process.stdout.write(`inverse-verify bundle: ${path.relative(root, bundlePath)} (${bundle.length} bytes)\n`);
  process.stdout.write(`markers manifest:      ${path.relative(root, markersPath)}\n`);
  process.stdout.write("-".repeat(82) + "\n");

  const failures = [];

  process.stdout.write("POSITIVE (must be present):\n");
  for (const m of manifest.positive || []) {
    const min = typeof m.min === "number" ? m.min : 1;
    const c = countLiteral(bundle, m.literal);
    const ok = c >= min;
    if (!ok) failures.push(`positive "${m.literal}" count ${c} < min ${min}`);
    process.stdout.write(`  ${ok ? "OK  " : "FAIL"} ${JSON.stringify(m.literal)} = ${c} (min ${min})${m.note ? `  — ${m.note}` : ""}\n`);
  }

  process.stdout.write("NEGATIVE (must be absent):\n");
  for (const m of manifest.negative || []) {
    const c = countLiteral(bundle, m.literal);
    const ok = c === 0;
    if (!ok) failures.push(`negative "${m.literal}" count ${c} != 0`);
    process.stdout.write(`  ${ok ? "OK  " : "FAIL"} ${JSON.stringify(m.literal)} = ${c} (want 0)${m.note ? `  — ${m.note}` : ""}\n`);
  }

  process.stdout.write("=".repeat(82) + "\n");
  if (failures.length === 0) {
    process.stdout.write("INVERSE-VERIFY: PASS — every positive present, every negative absent in the fresh bundle.\n");
    process.exit(0);
  }
  process.stdout.write(`INVERSE-VERIFY: FAIL — ${failures.length} marker divergence(s):\n`);
  for (const f of failures) process.stdout.write(`  - ${f}\n`);
  process.exit(1);
}

main();
