#!/usr/bin/env node

"use strict";

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

// ─── Module Imports ──────────────────────────────────────────────────────────

const {
  PACKAGE_NAME,
  PACKAGE_ROOT,
  GLOBAL_CLAUDE_DIR,
  EXIT_SUCCESS,
  EXIT_ERROR,
  EXIT_INTERRUPTED,
  EXIT_DRIFT,
  LOCK_STALE_MS,
  CHECKSUM_DISPLAY_LENGTH,
  LEGACY_PACKAGE_NAME,
  MARKETPLACE_REPO,
  MARKETPLACE_KEY,
  STANDARDS_REPO,
  STANDARDS_CACHE_DIR,
  ARCHIVED_IDS,
  SAFETY_HOOK_DEFINITIONS,
  SAFETY_HOOK_FILENAMES,
  INSTALL_TARGETS,
} = require("./lib/constants");

const {
  output,
  getTheme,
  log,
  logHeader,
  logSuccess,
  logError,
} = require("./lib/logging");

const {
  copyDir,
  countFiles,
  cleanupDeprecatedSkills,
  computeFileChecksum,
  computeDirChecksums,
  isBinaryFile,
} = require("./lib/fs-utils");

const { acquireLock, releaseLock } = require("./lib/locking");

const {
  MIGRATIONS,
  readMigrationState,
  writeMigrationState,
  runMigrations,
} = require("./lib/migrations");

const {
  verifyHookFiles,
  registerSettingsEntries,
  writeGlobalEnv,
} = require("./lib/settings");

const {
  isSafetyHook,
  isHookPresent,
  injectHook,
  readProtectionState,
  writeProtectionState,
  disableProtections,
  enableProtections,
} = require("./lib/protections");

const {
  compareDirs,
  verifyInstallation,
  showStatus,
  runDoctor,
  computeDiff,
  previewDiff,
  parseFrontmatter,
  generateAgentsMd,
} = require("./lib/diagnostics");

const {
  ensureMarketplace,
  syncStandardsRepo,
  installSkills,
  installAssets,
  syncExternal,
  installGlobal,
  runPremiumInstall,
  savePreferences,
  writeRegistry,
} = require("./lib/install-phases");

const { cleanupLegacy, uninstallGlobal } = require("./lib/cleanup");

// ─── Prerequisite Pre-check ──────────────────────────────────────────────────

// Platform-specific "how to install jq" line — the fix half of the error
// message. jq has no universal package name/manager, so tailor per OS.
function jqInstallHint(platform) {
  if (platform === "win32") {
    return "Install: 'winget install jqlang.jq' or 'scoop install jq'";
  }
  if (platform === "darwin") {
    return "Install: 'brew install jq' (or see https://jqlang.github.io/jq/download/)";
  }
  if (platform === "linux") {
    return (
      "Install: 'sudo apt-get install jq' (Debian/Ubuntu), " +
      "'sudo dnf install jq' (Fedora/RHEL), or 'sudo apk add jq' (Alpine)"
    );
  }
  return "Install jq: https://jqlang.github.io/jq/download/";
}

// Hard-fail install if a required external tool is missing, so the install
// refuses rather than producing a configured-but-bricked scaffold.
// ADR-031 §2: three lines -- the fact, the reason, the fix. No bypass flag.
//
// Name kept (historically Windows-only) for backward compat with external
// consumers; jq is now checked on every platform (see below).
function checkWindowsPrerequisites({ platform, executableExists }) {
  // bash: Windows-only. ADR-031 §4 — bash ships in every supported POSIX base
  // install, so the precheck is meaningful only on win32 (Git Bash / equivalent).
  if (platform === "win32" && !executableExists("bash")) {
    throw new Error(
      "bash.exe not found on PATH.\n" +
        "ECK requires Git Bash or an equivalent POSIX shell to run scaffold hooks.\n" +
        "Install Git for Windows: https://git-scm.com/download/win",
    );
  }

  // jq: cross-platform. Every ECK hook parses Claude Code's JSON input with jq;
  // without it the PreToolUse guards exit 2 and block all Edit/Write/Bash, so a
  // jq-less install is a bricked install on any OS (see #36, #90). macOS in
  // particular ships no jq by default.
  if (!executableExists("jq")) {
    throw new Error(
      "jq not found on PATH.\n" +
        "ECK hooks require jq to parse Claude Code input; without it every Edit, " +
        "Write, and Bash is blocked by the guard hooks.\n" +
        jqInstallHint(platform),
    );
  }
}

// Backward-compat alias — kept for external consumers that imported the v0.8.0 name.
// Internally we call checkWindowsPrerequisites directly.
function checkWindowsBash(opts) {
  return checkWindowsPrerequisites(opts);
}

// ─── CLI Entrypoint ──────────────────────────────────────────────────────────

function printHelp() {
  const t = getTheme();

  // Compact logo (no tagline)
  try {
    const { displayLogo } = require("./ui/logo");
    console.log(displayLogo({ showTagline: false, showVersion: true }));
  } catch {
    console.log(`\n  ${PACKAGE_NAME}\n`);
  }

  console.log(t.bold("Usage:"));
  console.log(`  npm install -g ${PACKAGE_NAME}   Install + auto-deploy`);
  console.log(
    `  evolv-coder-kit                                   Re-run / upgrade`,
  );
  console.log(
    `  evolv-coder-kit ${t.info("--help")}                            Show this help`,
  );
  console.log("");
  console.log(t.bold("Install Options:"));
  console.log(
    `  ${t.info("--global")}       Alias for default (backward compat)`,
  );
  console.log(
    `  ${t.info("--overwrite")}    Overwrite existing files during install`,
  );
  console.log(
    `  ${t.info("--dry-run")}      Show what would be installed without copying`,
  );
  console.log(
    `  ${t.info("--custom-mcp")}   Also install custom MCP primitives from scaffold/custom/`,
  );
  console.log(
    `  ${t.info("--configure")}    Re-run the setup interview to change preferences`,
  );
  console.log(
    `  ${t.info("--quiet")}        Minimal output (no logo, no box, no progress)`,
  );
  console.log(
    `  ${t.info("--verbose")}      Show file-level detail during install`,
  );
  console.log("");
  console.log(t.bold("Diagnostics:"));
  console.log(
    `  ${t.info("--verify")}       Check installed files for drift against package source`,
  );
  console.log(
    `  ${t.info("--status")}       Show installation health dashboard`,
  );
  console.log(`  ${t.info("--doctor")}       Run diagnostic checks`);
  console.log(
    `  ${t.info("--diff")}         Show unified diffs of files that would change`,
  );
  console.log("");
  console.log(t.bold("Safety:"));
  console.log(
    `  ${t.info("--enable-protections")}   Add safety hooks (session-scoped by default)`,
  );
  console.log(
    `    ${t.info("--all")}                Enable all 4 safety hooks (default)`,
  );
  console.log(
    `    ${t.info("--protect-files")}      Enable protect-files only`,
  );
  console.log(
    `    ${t.info("--detect-secrets")}     Enable detect-secrets only`,
  );
  console.log(
    `    ${t.info("--hitl-approval")}      Enable hitl-approval only`,
  );
  console.log(
    `    ${t.info("--security-gate")}      Enable security-gate only`,
  );
  console.log(
    `    ${t.info("--always-on")}          Make permanent (no session auto-restore)`,
  );
  console.log(`  ${t.info("--disable-protections")}  Remove safety hooks`);
  console.log("");
  console.log(t.bold("Lifecycle:"));
  console.log(
    `  ${t.info("--disable")}     Turn ECK off (preserves state — reverse with --enable)`,
  );
  console.log(`  ${t.info("--enable")}      Restore ECK after --disable`);
  console.log("");
  console.log(t.bold("Cleanup:"));
  console.log(
    `  ${t.info("--uninstall")}          Remove all ECK-owned files (preserves user files)`,
  );
  console.log(
    `  ${t.info("--cleanup-legacy")}     Remove all @evolvconsulting/claude-code-kit artifacts`,
  );
  console.log(
    `  ${t.info("--reset-migrations")}  Reset migration state (re-runs all migrations on next install)`,
  );
  console.log("");
  console.log(t.bold("Other:"));
  console.log(
    `  ${t.info("--generate-agents-md")}  Generate AGENTS.md from agent definitions`,
  );
  console.log(
    `  ${t.info("--path")} DIR     Specify agents directory (default: .claude/agents)`,
  );
  console.log(`  ${t.info("--logo")}         Print the evolv brand logo`);
  console.log(`  ${t.info("--help")}         Show help`);
  console.log(`  ${t.info("--version")}      Show version`);
  console.log("");
  console.log(t.bold("What gets installed (globally):"));
  console.log("  ~/.claude/skills/eck-*/            Global skills");
  console.log(
    "  ~/.claude/evolv-coder-kit/         Project-kickstart scaffold",
  );
  console.log(
    "  ~/.claude/hooks/                   Hooks (update checker, statusline)",
  );
  console.log(
    "  ~/.claude/output-modes/            Output modes (guided, standard, concise, pair, review)",
  );
  console.log(
    "  ~/.claude/evolv-coder-standards/   Standards repo (git clone)",
  );
  console.log("");
  console.log(
    t.dim(
      "After install, use /eck:new-project in Claude Code to scaffold projects.",
    ),
  );
  console.log("");
}

function printVersion() {
  const pkg = require(path.join(PACKAGE_ROOT, "package.json"));
  console.log(pkg.version);
}

function printLogo() {
  try {
    const { displayLogo } = require("./ui/logo");
    process.stdout.write(displayLogo({ showTagline: true, showVersion: true }));
  } catch {
    const pkg = require(path.join(PACKAGE_ROOT, "package.json"));
    process.stdout.write(
      `\n  @evolvconsulting/evolv-coder-kit v${pkg.version}\n\n`,
    );
  }
}

async function main() {
  // Signal handlers for clean interruption
  const signalCleanup = () => {
    releaseLock();
    log("");
    log("Installation interrupted.");
    process.exit(EXIT_INTERRUPTED);
  };
  process.on("SIGINT", signalCleanup);
  process.on("SIGTERM", signalCleanup);

  // Prerequisite hard-fail (jq on all platforms, bash on Windows) runs later,
  // only for real install/configure operations — not for informational or
  // management commands (--help, --version, --doctor, --verify, --status, the
  // protection/lifecycle toggles). Gating those would make --doctor, the very
  // tool meant to diagnose a missing jq, unreachable when jq is missing.

  // Node.js version pre-flight check
  const [major] = process.versions.node.split(".").map(Number);
  if (major < 18) {
    logError(
      `Node.js >= 18.0.0 is required (current: ${process.versions.node}).`,
    );
    log("  Upgrade Node.js: https://nodejs.org/");
    process.exit(EXIT_ERROR);
  }

  const args = process.argv.slice(2);

  if (args.includes("--help") || args.includes("-h")) {
    printHelp();
    return;
  }

  if (args.includes("--version") || args.includes("-v")) {
    printVersion();
    return;
  }

  if (args.includes("--logo")) {
    printLogo();
    return;
  }

  const knownFlags = [
    "--global",
    "--overwrite",
    "--dry-run",
    "--custom-mcp",
    "--verify",
    "--status",
    "--doctor",
    "--diff",
    "--generate-agents-md",
    "--path",
    "--disable-protections",
    "--enable-protections",
    "--all",
    "--protect-files",
    "--detect-secrets",
    "--hitl-approval",
    "--security-gate",
    "--always-on",
    "--cleanup-legacy",
    "--reset-migrations",
    "--uninstall",
    "--disable",
    "--enable",
    "--quiet",
    "--verbose",
    "--configure",
    "--help",
    "-h",
    "--version",
    "-v",
    "--logo",
  ];
  // Build set of argument indices that are values for flags (e.g., --path <dir>)
  const flagArgIndices = new Set();
  for (let i = 0; i < args.length; i++) {
    if (args[i] === "--path" && i + 1 < args.length) {
      flagArgIndices.add(i + 1);
    }
  }
  const unknownFlags = args.filter(
    (a, i) =>
      a.startsWith("-") && !knownFlags.includes(a) && !flagArgIndices.has(i),
  );
  if (unknownFlags.length > 0) {
    logError(
      `Unknown flag${unknownFlags.length > 1 ? "s" : ""}: ${unknownFlags.join(", ")}`,
    );
    log("Run with --help for usage information.");
    process.exit(EXIT_ERROR);
  }

  // Output level: --quiet and --verbose are mutually exclusive
  if (args.includes("--quiet") && args.includes("--verbose")) {
    logError("--quiet and --verbose are mutually exclusive");
    process.exit(EXIT_ERROR);
  }
  if (args.includes("--quiet")) output.level = "quiet";
  if (args.includes("--verbose")) output.level = "verbose";

  // Lifecycle flags mutual exclusion
  const lifecycleFlags = ["--enable", "--disable"];
  const conflictingFlags = [
    "--uninstall",
    "--cleanup-legacy",
    "--enable-protections",
    "--disable-protections",
    "--reset-migrations",
    "--configure",
    "--overwrite",
  ];
  if (args.includes("--enable") && args.includes("--disable")) {
    logError("--enable and --disable are mutually exclusive");
    process.exit(EXIT_ERROR);
  }
  for (const lc of lifecycleFlags) {
    if (args.includes(lc)) {
      for (const cf of conflictingFlags) {
        if (args.includes(cf)) {
          logError(`${lc} cannot be combined with ${cf}`);
          process.exit(EXIT_ERROR);
        }
      }
    }
  }

  if (args.includes("--status")) {
    showStatus();
    return;
  }

  if (args.includes("--doctor")) {
    const checks = runDoctor();
    const t = getTheme();
    logHeader("Doctor");
    let hasFailure = false;
    for (const c of checks) {
      const icon =
        c.status === "ok"
          ? t.success(t.symbols.complete)
          : c.status === "warn"
            ? t.warning(t.symbols.progress)
            : t.error(t.symbols.error);
      log(`${icon} ${c.check}: ${c.message}`);
      if (c.status === "fail") hasFailure = true;
    }
    process.exit(hasFailure ? EXIT_ERROR : EXIT_SUCCESS);
  }

  if (args.includes("--generate-agents-md")) {
    let agentsDir;
    if (args.includes("--path")) {
      const pathIdx = args.indexOf("--path");
      agentsDir = args[pathIdx + 1];
      if (!agentsDir || agentsDir.startsWith("-")) {
        logError("--path requires a directory argument");
        process.exit(EXIT_ERROR);
      }
      agentsDir = path.resolve(agentsDir);
      if (!fs.existsSync(agentsDir)) {
        logError(`--path directory does not exist: ${agentsDir}`);
        process.exit(EXIT_ERROR);
      }
      if (!fs.statSync(agentsDir).isDirectory()) {
        logError(`--path must be a directory, not a file: ${agentsDir}`);
        process.exit(EXIT_ERROR);
      }
    } else {
      agentsDir = path.join(
        GLOBAL_CLAUDE_DIR,
        "evolv-coder-kit",
        ".claude",
        "agents",
      );
    }
    const md = generateAgentsMd(agentsDir);
    if (md) {
      console.log(md);
    } else {
      log("No agent definitions found.");
    }
    return;
  }

  if (args.includes("--diff")) {
    const result = previewDiff();
    if (result.diffs.length === 0) {
      log("No differences found.");
    } else {
      logHeader(`Diff Preview (${result.totalFiles} files)`);
      for (const d of result.diffs) {
        log("");
        for (const line of d.lines) {
          log(line);
        }
      }
    }
    return;
  }

  if (args.includes("--verify")) {
    const result = verifyInstallation();
    const t = getTheme();
    logHeader("Verify Installation");
    if (result.modified.length > 0) {
      for (const f of result.modified) log(`${t.warning("[M]")} ${f.path}`);
    }
    if (result.added.length > 0) {
      for (const f of result.added) log(`${t.success("[A]")} ${f.path}`);
    }
    if (result.removed.length > 0) {
      for (const f of result.removed) log(`${t.error("[D]")} ${f.path}`);
    }
    log("");
    log(
      result.clean
        ? t.success("Installation is clean.")
        : `Drift detected: ${result.summary}`,
    );
    process.exit(result.clean ? EXIT_SUCCESS : EXIT_DRIFT);
  }

  if (args.includes("--disable-protections")) {
    const dryRun = args.includes("--dry-run");
    const result = disableProtections({ dryRun });
    if (result.success) {
      logSuccess(result.message);
    } else {
      logError(result.message);
      process.exit(EXIT_ERROR);
    }
    return;
  }

  if (args.includes("--enable-protections")) {
    const dryRun = args.includes("--dry-run");
    const result = enableProtections({ dryRun });
    if (result.success) {
      logSuccess(result.message);
    } else {
      logError(result.message);
      process.exit(EXIT_ERROR);
    }
    return;
  }

  if (args.includes("--uninstall")) {
    const dryRun = args.includes("--dry-run");
    uninstallGlobal({ dryRun });
    process.exit(EXIT_SUCCESS);
  }

  if (args.includes("--cleanup-legacy")) {
    const dryRun = args.includes("--dry-run");
    cleanupLegacy({ dryRun });
    return;
  }

  if (args.includes("--disable")) {
    const dryRun = args.includes("--dry-run");
    const { disableECK } = require("./lib/lifecycle");
    const result = disableECK({ dryRun });
    if (!result.success) process.exit(EXIT_ERROR);
    process.exit(EXIT_SUCCESS);
  }

  if (args.includes("--enable")) {
    const dryRun = args.includes("--dry-run");
    const { enableECK } = require("./lib/lifecycle");
    const result = enableECK({ dryRun });
    if (!result.success) process.exit(EXIT_ERROR);
    process.exit(EXIT_SUCCESS);
  }

  if (args.includes("--reset-migrations")) {
    const stateFile = path.join(
      GLOBAL_CLAUDE_DIR,
      "evolv-coder-kit",
      ".migrations",
    );
    const oldState = readMigrationState(stateFile);
    const newState = {
      schemaVersion: 1,
      appliedMigrations: [],
      lastRunVersion: null,
      lastRunAt: null,
      migrationDetails: {},
      firstInstalledAt: oldState.firstInstalledAt, // preserve
    };
    writeMigrationState(newState, stateFile);
    log("Migration state reset. All migrations will re-run on next install.");
    if (oldState.firstInstalledAt) {
      log(`  firstInstalledAt preserved: ${oldState.firstInstalledAt}`);
    }
    return;
  }

  const overwrite = args.includes("--overwrite");
  const dryRun = args.includes("--dry-run");
  const configure = args.includes("--configure");

  // Prerequisite hard-fail for real install/configure operations. Runs here
  // (after the informational/management early-returns above) so --doctor and
  // friends stay reachable when jq is missing.
  try {
    const { execSync } = require("child_process");
    checkWindowsPrerequisites({
      platform: process.platform,
      executableExists: (cmd) => {
        // `where` is the cmd/PowerShell builtin on Windows (ADR-031 §3);
        // `command -v` is the POSIX equivalent on macOS/Linux. Using `where`
        // everywhere would falsely report jq missing on Unix.
        const probe =
          process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
        try {
          execSync(probe, { stdio: "ignore" });
          return true;
        } catch {
          return false;
        }
      },
    });
  } catch (err) {
    logError(err.message);
    process.exit(EXIT_ERROR);
  }

  // Handle --custom-mcp flag (runs after install in all paths)
  const handleCustomMcp = () => {
    if (!args.includes("--custom-mcp")) return;
    const customSrc = path.join(PACKAGE_ROOT, "scaffold", "custom");
    if (fs.existsSync(customSrc)) {
      const customTrackerSrc = path.join(customSrc, "tracker");
      if (fs.existsSync(customTrackerSrc)) {
        const trackerDest = path.join(
          GLOBAL_CLAUDE_DIR,
          "evolv-coder-kit",
          "primitives",
          "tracker",
        );
        for (const dir of fs.readdirSync(customTrackerSrc)) {
          const src = path.join(customTrackerSrc, dir);
          const dest = path.join(trackerDest, dir);
          if (fs.statSync(src).isDirectory()) {
            copyDir(src, dest, { overwrite, dryRun });
          }
        }
      }
      const customDocsSrc = path.join(customSrc, "docs");
      if (fs.existsSync(customDocsSrc)) {
        const docsDest = path.join(
          GLOBAL_CLAUDE_DIR,
          "evolv-coder-kit",
          "primitives",
          "docs",
        );
        for (const dir of fs.readdirSync(customDocsSrc)) {
          const src = path.join(customDocsSrc, dir);
          const dest = path.join(docsDest, dir);
          if (fs.statSync(src).isDirectory()) {
            copyDir(src, dest, { overwrite, dryRun });
          }
        }
      }
      if (output.level !== "quiet")
        console.log("  Custom MCP primitives installed");
    } else {
      if (output.level !== "quiet")
        console.log("  No custom MCP primitives found in scaffold/custom/");
    }
  };

  if (output.level === "verbose") {
    // Verbose: logo + old detailed output
    try {
      const { displayLogo } = require("./ui/logo");
      console.log(displayLogo({ showTagline: false, showVersion: true }));
    } catch {
      /* best-effort */
    }
    await installGlobal({ overwrite, dryRun });
    handleCustomMcp();
    if (!dryRun) {
      savePreferences({
        tool: "claude-code",
        outputMode: "standard",
        devRigor: "standard",
        source: "default",
      });
    }
  } else if (output.level === "quiet") {
    // Quiet (--quiet flag): silent install, no interview
    await installGlobal({ overwrite, dryRun, silent: true });
    handleCustomMcp();
    if (!dryRun) {
      savePreferences({
        tool: "claude-code",
        outputMode: "standard",
        devRigor: "standard",
        source: "default",
      });
    }
  } else {
    // Normal: interactive interview + premium install UI
    // Interview module handles non-TTY gracefully (clack → readline fallback)
    await runInteractive({ overwrite, dryRun, configure });
    handleCustomMcp();
  }

  if (!dryRun) {
    const globalSettingsPath = path.join(GLOBAL_CLAUDE_DIR, "settings.json");
    const eckHome = path.join(
      require("os").homedir(),
      ".claude",
      "evolv-coder-kit",
    );
    writeGlobalEnv(globalSettingsPath, "ECK_HOME", eckHome);
  }

  process.exit(EXIT_SUCCESS);
}

/**
 * Interactive install path: runs the interview wizard, then premium install.
 * The interview collects user preferences (output mode, dev rigor, protections).
 * On existing installs, asks about overwrite behavior.
 * --configure flag forces re-interview even if preferences exist.
 */
async function runInteractive(options = {}) {
  const {
    overwrite: cliOverwrite = false,
    dryRun = false,
    configure = false,
  } = options;

  const { runInterview } = require("./ui/interview");

  // Detect existing install via .migrations file
  const migrationsPath = path.join(
    GLOBAL_CLAUDE_DIR,
    "evolv-coder-kit",
    ".migrations",
  );
  const existingInstall = fs.existsSync(migrationsPath);

  // Read existing preferences (if any)
  const prefsPath = path.join(
    GLOBAL_CLAUDE_DIR,
    "evolv-coder-kit",
    "user-preferences.json",
  );
  let existingPrefs = null;
  try {
    existingPrefs = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
  } catch {
    /* first install */
  }
  const currentVersion = existingPrefs ? existingPrefs.version : undefined;

  // Skip interview only if user has actively configured via interview before
  // Default prefs (from postinstall/quiet install) don't count — user should still see the interview
  const userHasConfigured =
    existingPrefs && existingPrefs.source === "interview";

  // Skip interview when: user already configured, OR stdin is not interactive (CI, piped, tests)
  const canInteract = process.stdin.isTTY || false;
  const skipInterview = (userHasConfigured && !configure) || !canInteract;

  let result;
  if (skipInterview) {
    // Non-interactive or already configured — go straight to premium install
    try {
      const { displayLogo } = require("./ui/logo");
      console.log(displayLogo({ showTagline: false, showVersion: true }));
    } catch {
      /* best-effort */
    }
    await runPremiumInstall({ overwrite: cliOverwrite, dryRun });
    if (!dryRun && !userHasConfigured) {
      savePreferences({
        tool: "claude-code",
        outputMode: "standard",
        devRigor: "standard",
        source: "default",
      });
    }
    return;
  }

  // Run interview (shows its own logo)
  result = await runInterview({ existingInstall, currentVersion });

  // Derive overwrite from interview result
  const overwrite = result.overwriteMode === "overwrite" || cliOverwrite;

  // User chose "skip" in overwrite screen
  if (result.overwriteMode === "skip") {
    if (!dryRun) {
      savePreferences({ ...result, source: "interview" });
    }
    return;
  }

  // Build extra summary items from interview choices
  const extraSummaryItems = [
    { label: "Output Mode", value: result.outputMode },
    { label: "Dev Rigor", value: result.devRigor },
    { label: "Protections", value: result.protections },
  ];

  await runPremiumInstall({ overwrite, dryRun, extraSummaryItems });

  if (!dryRun) {
    savePreferences({ ...result, source: "interview" });
  }
}

if (require.main === module) {
  main().catch((err) => {
    logError(err.message);
    process.exit(EXIT_ERROR);
  });
}

// ─── Module Exports ──────────────────────────────────────────────────────────
// All 48+ original exports preserved. New exports are additive only.
// See tests/install-compat.test.cjs for snapshot validation.
module.exports = {
  // Constants
  ARCHIVED_IDS,
  MIGRATIONS,
  INSTALL_TARGETS,
  EXIT_SUCCESS,
  EXIT_ERROR,
  EXIT_INTERRUPTED,
  CHECKSUM_DISPLAY_LENGTH,
  LOCK_STALE_MS,
  EXIT_DRIFT,
  SAFETY_HOOK_DEFINITIONS,
  SAFETY_HOOK_FILENAMES,
  MARKETPLACE_REPO,
  MARKETPLACE_KEY,
  STANDARDS_REPO,
  STANDARDS_CACHE_DIR,
  LEGACY_PACKAGE_NAME,
  // State & migrations
  readMigrationState,
  writeMigrationState,
  cleanupDeprecatedSkills,
  runMigrations,
  // Settings & cleanup
  registerSettingsEntries,
  cleanupLegacy,
  uninstallGlobal,
  // File operations
  copyDir,
  countFiles,
  acquireLock,
  releaseLock,
  computeFileChecksum,
  computeDirChecksums,
  verifyInstallation,
  compareDirs,
  computeDiff,
  previewDiff,
  isBinaryFile,
  // Commands
  showStatus,
  runDoctor,
  parseFrontmatter,
  generateAgentsMd,
  isSafetyHook,
  isHookPresent,
  injectHook,
  readProtectionState,
  writeProtectionState,
  disableProtections,
  enableProtections,
  ensureMarketplace,
  syncStandardsRepo,
  // New in Epic #3
  output,
  installGlobal,
  savePreferences,
  writeRegistry,
  // New in Premium Install
  installSkills,
  installAssets,
  syncExternal,
  runPremiumInstall,
  // New in v0.5.2
  verifyHookFiles,
  // New in v0.7.0 — Template Engine
  ...require("./lib/template-engine"),
  // New in v0.8.0 — Windows bash pre-check (now Windows prerequisites)
  checkWindowsBash,
  checkWindowsPrerequisites,
  // New in v0.8.x — ECK enable/disable lifecycle
  ...require("./lib/lifecycle"),
  stripECKSettings: require("./lib/cleanup").stripECKSettings,
  DISABLED_STATE_FILE: require("./lib/constants").DISABLED_STATE_FILE,
};
