"use strict";

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

const {
  PACKAGE_ROOT,
  GLOBAL_CLAUDE_DIR,
  INSTALL_TARGETS,
  MARKETPLACE_REPO,
  MARKETPLACE_KEY,
  STANDARDS_REPO,
  STANDARDS_CACHE_DIR,
} = require("./constants");
const {
  output,
  getTheme,
  log,
  logHeader,
  logSuccess,
  logSkip,
  logError,
  logWarning,
  logVerbose,
} = require("./logging");
const {
  copyDir,
  cleanupDeprecatedSkills,
  countFiles,
  computeFileChecksum,
} = require("./fs-utils");
const { acquireLock, releaseLock } = require("./locking");
const { runMigrations } = require("./migrations");
const { registerSettingsEntries } = require("./settings");
const {
  checkNpmPrefixOnPath,
  printNpmPrefixWarning,
} = require("./windows-path-detection");

// ─── Marketplace ────────────────────────────────────────────────────────────

function ensureMarketplace({ dryRun = false } = {}) {
  const marketplacesFile = path.join(
    GLOBAL_CLAUDE_DIR,
    "plugins",
    "known_marketplaces.json",
  );

  // Check if already registered
  if (fs.existsSync(marketplacesFile)) {
    try {
      const data = JSON.parse(fs.readFileSync(marketplacesFile, "utf8"));
      if (data[MARKETPLACE_KEY]) {
        return { installed: false, alreadyRegistered: true };
      }
    } catch {
      // Corrupted file — proceed to install
    }
  }

  // Check if claude CLI is available
  try {
    execSync("claude --version", { stdio: "ignore", timeout: 10000 });
  } catch {
    return {
      installed: false,
      alreadyRegistered: false,
      error: "claude CLI not found",
    };
  }

  if (dryRun) {
    return { installed: false, alreadyRegistered: false, dryRun: true };
  }

  try {
    execSync(`claude plugin marketplace add ${MARKETPLACE_REPO}`, {
      stdio: "pipe",
      timeout: 30000,
      windowsHide: true,
      // On Windows, `claude plugin marketplace add` shells out to `git clone`.
      // Without these env vars, git defers to git-credential-manager, which prompts
      // for credentials even for public repos -- and with no TTY, the prompt never
      // resolves. The child hangs past our timeout, outlives the parent, and holds
      // file locks on the marketplace dir indefinitely. See #30.
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: "0",
        GCM_INTERACTIVE: "Never",
      },
    });
    return { installed: true, alreadyRegistered: false };
  } catch (err) {
    return { installed: false, alreadyRegistered: false, error: err.message };
  }
}

// ─── Standards Repository ──────────────────────────────────────────────────

function syncStandardsRepo({ dryRun = false } = {}) {
  // Check if gh CLI is available
  try {
    execSync("gh --version", { stdio: "ignore", timeout: 10000 });
  } catch {
    return { synced: false, error: "gh CLI not found" };
  }

  const isUpdate = fs.existsSync(path.join(STANDARDS_CACHE_DIR, ".git"));

  if (dryRun) {
    return {
      synced: false,
      dryRun: true,
      action: isUpdate ? "update" : "clone",
    };
  }

  try {
    if (isUpdate) {
      execSync("git pull --ff-only", {
        cwd: STANDARDS_CACHE_DIR,
        stdio: "pipe",
        timeout: 30000,
      });
      return { synced: true, action: "update" };
    } else {
      execSync(`gh repo clone ${STANDARDS_REPO} "${STANDARDS_CACHE_DIR}"`, {
        stdio: "pipe",
        timeout: 60000,
      });
      return { synced: true, action: "clone" };
    }
  } catch (err) {
    return {
      synced: false,
      action: isUpdate ? "update" : "clone",
      error: err.message,
    };
  }
}

// ─── Install Skills ─────────────────────────────────────────────────────────

function installSkills({
  overwrite = false,
  dryRun = false,
  silent = false,
} = {}) {
  const target = INSTALL_TARGETS.global.skills;
  const copyOptions = overwrite
    ? { overwrite: true, dryRun }
    : { overwriteIfChanged: true, dryRun };

  let copied = 0,
    updated = 0,
    skipped = 0,
    removed = 0;
  const removedDirs = [];

  if (!silent) log(`${target.label}`);

  const fileCount = countFiles(target.src);
  if (fileCount === 0) {
    if (!silent)
      logSkip(`No files found in ${path.relative(PACKAGE_ROOT, target.src)}`);
    return { copied, updated, skipped, removed, removedDirs, skillCount: 0 };
  }

  const subdirs = fs
    .readdirSync(target.src, { withFileTypes: true })
    .filter((e) => e.isDirectory());

  for (const subdir of subdirs) {
    const srcPath = path.join(target.src, subdir.name);
    const destPath = path.join(target.dest, target.subDirPrefix + subdir.name);
    const result = copyDir(srcPath, destPath, copyOptions);
    copied += result.copied;
    updated += result.updated;
    skipped += result.skipped;
    if (!silent) {
      if (result.copied > 0) {
        logSuccess(
          `${result.copied} files ${dryRun ? "would be " : ""}copied to ${destPath}`,
        );
      }
      if (result.updated > 0) {
        logSuccess(
          `${result.updated} files ${dryRun ? "would be " : ""}updated in ${destPath}`,
        );
      }
      if (result.skipped > 0) {
        logSkip(`${result.skipped} files up to date`);
      }
    }
  }

  // Clean up deprecated skills
  const cleanup = cleanupDeprecatedSkills(target.src, target.dest, {
    prefix: target.subDirPrefix,
    dryRun,
  });
  removed += cleanup.removed;
  if (cleanup.removed > 0) {
    removedDirs.push(...cleanup.removedDirs);
    if (!silent) {
      for (const dir of cleanup.removedDirs) {
        logSuccess(
          `${dryRun ? "Would remove" : "Removed"} deprecated skill: ${dir}`,
        );
      }
    }
  }

  return {
    copied,
    updated,
    skipped,
    removed,
    removedDirs,
    skillCount: subdirs.length,
  };
}

// ─── Install Assets ─────────────────────────────────────────────────────────

function installAssets({
  overwrite = false,
  dryRun = false,
  silent = false,
} = {}) {
  const copyOptions = overwrite
    ? { overwrite: true, dryRun }
    : { overwriteIfChanged: true, dryRun };

  let copied = 0,
    updated = 0,
    skipped = 0;
  const targets = [];

  for (const [key, target] of Object.entries(INSTALL_TARGETS.global)) {
    if (target.subDirPrefix) continue; // skills handled by installSkills()

    if (!silent) log(`${target.label}`);
    targets.push(key);

    const fileCount = countFiles(target.src);
    if (fileCount === 0) {
      if (!silent)
        logSkip(`No files found in ${path.relative(PACKAGE_ROOT, target.src)}`);
      continue;
    }

    const result = copyDir(target.src, target.dest, copyOptions);
    copied += result.copied;
    updated += result.updated;
    skipped += result.skipped;

    if (!silent) {
      if (result.copied > 0) {
        logSuccess(
          `${result.copied} files ${dryRun ? "would be " : ""}copied to ${target.dest}`,
        );
      }
      if (result.updated > 0) {
        logSuccess(
          `${result.updated} files ${dryRun ? "would be " : ""}updated in ${target.dest}`,
        );
      }
      if (result.skipped > 0) {
        logSkip(`${result.skipped} files up to date`);
      }
    }
  }

  return { copied, updated, skipped, targets };
}

// ─── Sync External ──────────────────────────────────────────────────────────

function syncExternal({ dryRun = false, silent = false } = {}) {
  // Ensure evolv-marketplace is registered
  if (!silent) {
    log("");
    log("Evolv Marketplace");
  }
  const marketplace = ensureMarketplace({ dryRun });
  if (!silent) {
    if (marketplace.alreadyRegistered) {
      logSkip("evolv-marketplace already registered");
    } else if (marketplace.dryRun) {
      logSuccess(`Would register evolv-marketplace (${MARKETPLACE_REPO})`);
    } else if (marketplace.installed) {
      logSuccess(`Registered evolv-marketplace (${MARKETPLACE_REPO})`);
    } else if (marketplace.error) {
      log(
        `  [WARN] Could not register evolv-marketplace: ${marketplace.error}`,
      );
    }
  }

  // Sync evolv-coder-standards repository
  if (!silent) {
    log("");
    log("Standards Repository");
  }
  const standards = syncStandardsRepo({ dryRun });
  if (!silent) {
    if (standards.dryRun) {
      logSuccess(
        `Would ${standards.action} evolv-coder-standards: ${STANDARDS_CACHE_DIR}`,
      );
    } else if (standards.synced && standards.action === "clone") {
      logSuccess(`Standards repo cloned: ${STANDARDS_CACHE_DIR}`);
    } else if (standards.synced && standards.action === "update") {
      logSuccess(`Standards repo updated: ${STANDARDS_CACHE_DIR}`);
    } else if (standards.error) {
      log(`  [WARN] Could not sync evolv-coder-standards: ${standards.error}`);
    }
  }

  return { marketplace, standards };
}

// ─── Install Logic ───────────────────────────────────────────────────────────

async function installGlobal(options = {}) {
  const { overwrite = false, dryRun = false, silent = false } = options;

  // Silent mode suppresses log output (used when spinner is active)
  const prevSilent = output.isSilent;
  if (silent) output.isSilent = true;

  if (!dryRun) {
    const lock = acquireLock();
    if (!lock.acquired) {
      logError(lock.reason);
      return { copied: 0, updated: 0, skipped: 0, removed: 0, locked: true };
    }
  }

  try {
    logHeader("Installing globally to ~/.claude/");
    log("");

    // Pre-copy migrations
    const preMigrations = runMigrations("pre-copy", {
      dryRun,
      globalDir: GLOBAL_CLAUDE_DIR,
    });

    // Phase 1: Skills
    const skills = installSkills({ overwrite, dryRun });

    // Phase 2: Assets (scaffold, hooks, output-modes)
    const assets = installAssets({ overwrite, dryRun });

    // Phase 2b: Helper scripts (update-stage.sh, colors.sh → ~/.claude/evolv-coder-kit/)
    const helperScripts = ["update-stage.sh", "colors.sh"];
    let helperCopied = 0;
    for (const scriptName of helperScripts) {
      const helperSrc = path.join(PACKAGE_ROOT, "scripts", scriptName);
      const helperDest = path.join(
        GLOBAL_CLAUDE_DIR,
        "evolv-coder-kit",
        scriptName,
      );
      if (fs.existsSync(helperSrc)) {
        const needsCopy =
          !fs.existsSync(helperDest) ||
          computeFileChecksum(helperSrc) !== computeFileChecksum(helperDest);
        if (needsCopy) {
          if (!dryRun) {
            fs.mkdirSync(path.dirname(helperDest), { recursive: true });
            fs.copyFileSync(helperSrc, helperDest);
            fs.chmodSync(helperDest, 0o755);
          }
          helperCopied++;
          logSuccess(
            `${scriptName} ${dryRun ? "would be " : ""}installed to ~/.claude/evolv-coder-kit/`,
          );
        }
      }
    }

    // Phase 2b2: Global scripts (scaffold/global/scripts/*.js → ~/.claude/evolv-coder-kit/)
    const globalScriptsDir = path.join(
      PACKAGE_ROOT,
      "scaffold",
      "global",
      "scripts",
    );
    const globalScripts = fs.existsSync(globalScriptsDir)
      ? fs.readdirSync(globalScriptsDir).filter((f) => f.endsWith(".js"))
      : [];
    for (const scriptName of globalScripts) {
      const lcSrc = path.join(globalScriptsDir, scriptName);
      const lcDest = path.join(
        GLOBAL_CLAUDE_DIR,
        "evolv-coder-kit",
        scriptName,
      );
      if (fs.existsSync(lcSrc)) {
        const needsCopy =
          !fs.existsSync(lcDest) ||
          computeFileChecksum(lcSrc) !== computeFileChecksum(lcDest);
        if (needsCopy) {
          if (!dryRun) {
            fs.mkdirSync(path.dirname(lcDest), { recursive: true });
            fs.copyFileSync(lcSrc, lcDest);
          }
          helperCopied++;
          logSuccess(
            `${scriptName} ${dryRun ? "would be " : ""}installed to ~/.claude/evolv-coder-kit/`,
          );
        }
      }
    }

    // Phase 2b3: Template CLI → ~/.claude/evolv-coder-kit/bin/
    const { deployTemplateCli } = require("./template-cli-deploy");
    deployTemplateCli({
      overwrite,
      dryRun,
      copyDir,
      computeFileChecksum,
      logSuccess,
    });

    // Phase 2c: Global catalog → ~/.claude/evolv-coder-kit/global-catalog.yaml
    const catalogSrc = path.join(
      PACKAGE_ROOT,
      "scaffold",
      "global",
      "global-catalog.yaml",
    );
    const catalogDest = path.join(
      GLOBAL_CLAUDE_DIR,
      "evolv-coder-kit",
      "global-catalog.yaml",
    );
    let catalogCopied = 0;
    if (fs.existsSync(catalogSrc)) {
      const needsCopy =
        !fs.existsSync(catalogDest) ||
        computeFileChecksum(catalogSrc) !== computeFileChecksum(catalogDest);
      if (needsCopy) {
        if (!dryRun) {
          fs.mkdirSync(path.dirname(catalogDest), { recursive: true });
          fs.copyFileSync(catalogSrc, catalogDest);
        }
        catalogCopied = 1;
        logSuccess(
          `global-catalog.yaml ${dryRun ? "would be " : ""}installed to ~/.claude/evolv-coder-kit/`,
        );
      }
    }

    // Phase 2c2: Plugin manifest → ~/.claude/evolv-coder-kit/plugin-manifest.yaml
    const manifestSrc = path.join(
      PACKAGE_ROOT,
      "scaffold",
      "global",
      "plugin-manifest.yaml",
    );
    const manifestDest = path.join(
      GLOBAL_CLAUDE_DIR,
      "evolv-coder-kit",
      "plugin-manifest.yaml",
    );
    if (fs.existsSync(manifestSrc)) {
      const manifestNeedsCopy =
        !fs.existsSync(manifestDest) ||
        computeFileChecksum(manifestSrc) !== computeFileChecksum(manifestDest);
      if (manifestNeedsCopy) {
        if (!dryRun) {
          fs.mkdirSync(path.dirname(manifestDest), { recursive: true });
          fs.copyFileSync(manifestSrc, manifestDest);
        }
        logSuccess(
          `plugin-manifest.yaml ${dryRun ? "would be " : ""}installed to ~/.claude/evolv-coder-kit/`,
        );
      }
    }

    // Phase 2d: Statusline config → ~/.claude/evolv-coder-kit/statusline-config.json (no-clobber)
    const slConfigSrc = path.join(
      PACKAGE_ROOT,
      "scaffold",
      "global",
      "statusline-config.default.json",
    );
    const slConfigDest = path.join(
      GLOBAL_CLAUDE_DIR,
      "evolv-coder-kit",
      "statusline-config.json",
    );
    let slConfigCopied = 0;
    if (fs.existsSync(slConfigSrc) && !fs.existsSync(slConfigDest)) {
      if (!dryRun) {
        fs.mkdirSync(path.dirname(slConfigDest), { recursive: true });
        fs.copyFileSync(slConfigSrc, slConfigDest);
      }
      slConfigCopied = 1;
      logSuccess(
        `statusline-config.json ${dryRun ? "would be " : ""}installed to ~/.claude/evolv-coder-kit/ (no-clobber)`,
      );
    }

    // Phase 2e: Registry bootstrap
    const registry = writeRegistry({ dryRun });
    let registryCopied = registry.created ? 1 : 0;

    const totalCopied =
      skills.copied +
      assets.copied +
      helperCopied +
      catalogCopied +
      slConfigCopied +
      registryCopied;
    const totalUpdated = skills.updated + assets.updated;
    const totalSkipped = skills.skipped + assets.skipped;
    const totalRemoved = skills.removed;

    // Phase 3: Settings
    log("");
    log("Settings Registration");
    registerSettingsEntries({ dryRun, globalDir: GLOBAL_CLAUDE_DIR });

    // Post-copy migrations
    const postMigrations = runMigrations("post-copy", {
      dryRun,
      globalDir: GLOBAL_CLAUDE_DIR,
    });

    // Phase 4: External sync
    const external = syncExternal({ dryRun });

    const totalMigrations = preMigrations.ran + postMigrations.ran;
    const totalMigrationsFailed = preMigrations.failed + postMigrations.failed;

    log("");
    logHeader("Install Summary");
    log(`  Copied: ${totalCopied} files (new)`);
    log(`  Updated: ${totalUpdated} files`);
    log(`  Up to date: ${totalSkipped} files`);
    if (totalRemoved > 0) {
      log(`  Removed: ${totalRemoved} deprecated skills`);
    }
    if (totalMigrations > 0) {
      log(`  Migrations: ${totalMigrations} applied`);
    }
    if (totalMigrationsFailed > 0) {
      log(
        `  Migrations failed: ${totalMigrationsFailed} (will retry on next run)`,
      );
    }

    if ((totalCopied > 0 || totalUpdated > 0) && !dryRun) {
      log("");
      log("Next steps:");
      log("  1. Open Claude Code in any project");
      log("  2. Run /eck:help to see all available skills");
      log("  3. Run /eck:new-project to scaffold a new project");
      log(
        "  4. Run /eck:audit-unmanaged-project to audit an unmanaged project",
      );
    }

    if (!dryRun) {
      printNpmPrefixWarning(checkNpmPrefixOnPath());
    }

    // Plugin detection summary
    try {
      const settingsPath = path.join(
        require("os").homedir(),
        ".claude",
        "settings.json",
      );
      if (fs.existsSync(settingsPath)) {
        const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
        const plugins = settings.enabledPlugins || [];
        if (plugins.length > 0) {
          log("");
          log("  External Plugins");
          log(`    ${plugins.length} official plugin(s) detected`);
          log("    Run /eck:plugin-status for integration guidance");
        }
      }
    } catch {
      // Non-blocking — plugin detection is informational only
    }

    return {
      copied: totalCopied,
      updated: totalUpdated,
      skipped: totalSkipped,
      removed: totalRemoved,
      phases: { skills, assets, external },
    };
  } finally {
    output.isSilent = prevSilent;
    if (!options.dryRun) {
      releaseLock();
    }
  }
}

// ─── Premium Install ────────────────────────────────────────────────────────

async function runPremiumInstall(options = {}) {
  const { overwrite = false, dryRun = false, extraSummaryItems = [] } = options;

  if (!dryRun) {
    const lock = acquireLock();
    if (!lock.acquired) {
      logError(lock.reason);
      return { copied: 0, updated: 0, skipped: 0, removed: 0, locked: true };
    }
  }

  try {
    // Pre-copy migrations (silent, instant)
    runMigrations("pre-copy", { dryRun, globalDir: GLOBAL_CLAUDE_DIR });

    let skillsResult, assetsResult, settingsResult, externalResult;

    const { runTasks } = require("../ui/tasks");

    await runTasks([
      {
        title: "Installing skills",
        task: async () => {
          skillsResult = installSkills({ overwrite, dryRun, silent: true });
          const parts = [];
          parts.push(`${skillsResult.skillCount} skills`);
          const detail = [];
          if (skillsResult.copied > 0 || skillsResult.updated > 0) {
            if (skillsResult.updated > 0)
              detail.push(`${skillsResult.updated} updated`);
            if (skillsResult.copied > 0)
              detail.push(`${skillsResult.copied} new`);
          }
          const currentCount = skillsResult.skipped;
          if (currentCount > 0) detail.push(`${currentCount} current`);
          if (detail.length > 0) parts.push(`(${detail.join(", ")})`);
          return parts.join(" ");
        },
      },
      {
        title: "Installing scaffold",
        task: async () => {
          assetsResult = installAssets({ overwrite, dryRun, silent: true });
          // Helper scripts (update-stage.sh, colors.sh → ~/.claude/evolv-coder-kit/)
          for (const scriptName of ["update-stage.sh", "colors.sh"]) {
            const helperSrc = path.join(PACKAGE_ROOT, "scripts", scriptName);
            const helperDest = path.join(
              GLOBAL_CLAUDE_DIR,
              "evolv-coder-kit",
              scriptName,
            );
            if (fs.existsSync(helperSrc)) {
              const needsCopy =
                !fs.existsSync(helperDest) ||
                computeFileChecksum(helperSrc) !==
                  computeFileChecksum(helperDest);
              if (needsCopy && !dryRun) {
                fs.mkdirSync(path.dirname(helperDest), { recursive: true });
                fs.copyFileSync(helperSrc, helperDest);
                fs.chmodSync(helperDest, 0o755);
                assetsResult.copied = (assetsResult.copied || 0) + 1;
              }
            }
          }
          // Global scripts (scaffold/global/scripts/*.js → ~/.claude/evolv-coder-kit/)
          const globalScriptsDir = path.join(
            PACKAGE_ROOT,
            "scaffold",
            "global",
            "scripts",
          );
          const globalScripts = fs.existsSync(globalScriptsDir)
            ? fs.readdirSync(globalScriptsDir).filter((f) => f.endsWith(".js"))
            : [];
          for (const scriptName of globalScripts) {
            const lcSrc = path.join(globalScriptsDir, scriptName);
            const lcDest = path.join(
              GLOBAL_CLAUDE_DIR,
              "evolv-coder-kit",
              scriptName,
            );
            if (fs.existsSync(lcSrc)) {
              const needsCopy =
                !fs.existsSync(lcDest) ||
                computeFileChecksum(lcSrc) !== computeFileChecksum(lcDest);
              if (needsCopy && !dryRun) {
                fs.mkdirSync(path.dirname(lcDest), { recursive: true });
                fs.copyFileSync(lcSrc, lcDest);
                assetsResult.copied = (assetsResult.copied || 0) + 1;
              }
            }
          }
          // Global catalog → ~/.claude/evolv-coder-kit/global-catalog.yaml
          const catalogSrc = path.join(
            PACKAGE_ROOT,
            "scaffold",
            "global",
            "global-catalog.yaml",
          );
          const catalogDest = path.join(
            GLOBAL_CLAUDE_DIR,
            "evolv-coder-kit",
            "global-catalog.yaml",
          );
          if (fs.existsSync(catalogSrc)) {
            const needsCopy =
              !fs.existsSync(catalogDest) ||
              computeFileChecksum(catalogSrc) !==
                computeFileChecksum(catalogDest);
            if (needsCopy && !dryRun) {
              fs.mkdirSync(path.dirname(catalogDest), { recursive: true });
              fs.copyFileSync(catalogSrc, catalogDest);
              assetsResult.copied = (assetsResult.copied || 0) + 1;
            }
          }
          // Plugin manifest → ~/.claude/evolv-coder-kit/plugin-manifest.yaml
          const manifestSrc = path.join(
            PACKAGE_ROOT,
            "scaffold",
            "global",
            "plugin-manifest.yaml",
          );
          const manifestDest = path.join(
            GLOBAL_CLAUDE_DIR,
            "evolv-coder-kit",
            "plugin-manifest.yaml",
          );
          if (fs.existsSync(manifestSrc)) {
            const manifestNeedsCopy =
              !fs.existsSync(manifestDest) ||
              computeFileChecksum(manifestSrc) !==
                computeFileChecksum(manifestDest);
            if (manifestNeedsCopy && !dryRun) {
              fs.mkdirSync(path.dirname(manifestDest), { recursive: true });
              fs.copyFileSync(manifestSrc, manifestDest);
              assetsResult.copied = (assetsResult.copied || 0) + 1;
            }
          }
          // Statusline config → ~/.claude/evolv-coder-kit/statusline-config.json (no-clobber)
          const slConfigSrc = path.join(
            PACKAGE_ROOT,
            "scaffold",
            "global",
            "statusline-config.default.json",
          );
          const slConfigDest = path.join(
            GLOBAL_CLAUDE_DIR,
            "evolv-coder-kit",
            "statusline-config.json",
          );
          if (fs.existsSync(slConfigSrc) && !fs.existsSync(slConfigDest)) {
            if (!dryRun) {
              fs.mkdirSync(path.dirname(slConfigDest), { recursive: true });
              fs.copyFileSync(slConfigSrc, slConfigDest);
              assetsResult.copied = (assetsResult.copied || 0) + 1;
            }
          }
          const total =
            assetsResult.copied + assetsResult.updated + assetsResult.skipped;
          const parts = [`Templates installed (${total} files`];
          if (assetsResult.updated > 0) {
            parts[0] += `, ${assetsResult.updated} updated`;
          }
          return parts[0] + ")";
        },
      },
      {
        title: "Configuring settings",
        task: async () => {
          settingsResult = registerSettingsEntries({
            dryRun,
            globalDir: GLOBAL_CLAUDE_DIR,
            silent: true,
          });
          return settingsResult.registered > 0
            ? `${settingsResult.registered} settings ${dryRun ? "would be " : ""}registered`
            : "Settings up to date";
        },
      },
      {
        title: "Syncing standards",
        task: async () => {
          // Post-copy migrations
          const postMig = runMigrations("post-copy", {
            dryRun,
            globalDir: GLOBAL_CLAUDE_DIR,
          });
          if (postMig.failed > 0) {
            logWarning(
              `${postMig.failed} migration(s) failed: ${postMig.failedIds.join(", ")}`,
            );
          }
          externalResult = syncExternal({ dryRun, silent: true });
          const parts = [];
          if (externalResult.marketplace.alreadyRegistered) {
            parts.push("marketplace current");
          } else if (
            externalResult.marketplace.installed ||
            externalResult.marketplace.dryRun
          ) {
            parts.push("marketplace registered");
          }
          if (
            externalResult.standards.synced &&
            externalResult.standards.action === "update"
          ) {
            parts.push("standards updated");
          } else if (
            externalResult.standards.synced &&
            externalResult.standards.action === "clone"
          ) {
            parts.push("standards cloned");
          } else if (externalResult.standards.dryRun) {
            parts.push(`standards would ${externalResult.standards.action}`);
          } else {
            parts.push("standards current");
          }
          return parts.join(", ");
        },
      },
      {
        title: "Finalizing",
        task: async () => {
          writeGlobalState({ dryRun });
          writeRegistry({ dryRun });
          // Brief pause for visual perceptibility
          await new Promise((r) => setTimeout(r, 100));
          return "Installation complete";
        },
      },
    ]);

    // Aggregate results
    const totalCopied =
      (skillsResult?.copied || 0) + (assetsResult?.copied || 0);
    const totalUpdated =
      (skillsResult?.updated || 0) + (assetsResult?.updated || 0);
    const totalSkipped =
      (skillsResult?.skipped || 0) + (assetsResult?.skipped || 0);
    const totalRemoved = skillsResult?.removed || 0;

    // Summary box — context-aware messaging
    const { renderBox } = require("../ui/box");
    const t = getTheme();
    const filesInstalled = totalCopied + totalUpdated;

    let summaryTitle;
    let summaryItems;

    if (filesInstalled > 0) {
      // Fresh install or upgrade — files were changed
      summaryTitle = "Installation Complete";
      summaryItems = [{ label: "Installed", value: `${filesInstalled} files` }];
      if (totalSkipped > 0) {
        summaryItems.push({
          label: "Current",
          value: `${totalSkipped} files (unchanged)`,
        });
      }
      summaryItems.push(...extraSummaryItems);
    } else {
      // Everything already current
      summaryTitle = "Up to Date";
      summaryItems = [
        { label: "Verified", value: `${totalSkipped} files current` },
        ...extraSummaryItems,
      ];
    }

    console.log("");
    console.log(renderBox({ title: summaryTitle, items: summaryItems }));

    console.log("");
    if (filesInstalled > 0) {
      // Fresh install or upgrade — action-oriented next steps
      console.log(`  ${t.bold("Next steps:")}`);
      console.log("  1. Open Claude Code in any project");
      console.log("  2. Run /eck:help to see all available skills");
      console.log("  3. Run /eck:new-project to scaffold a new project");
      console.log(
        "  4. Run /eck:migrate-project to onboard an existing project",
      );
      console.log("  5. Run /eck:audit-unmanaged-project to discover projects");
    } else {
      // Up to date — quick reference
      console.log(`  ${t.bold("Quick reference:")}`);
      console.log("  1. Run evolv-coder-kit --configure to change preferences");
      console.log("  2. Run /eck:help in Claude Code for available skills");
    }
    console.log("");

    if (!dryRun) {
      printNpmPrefixWarning(checkNpmPrefixOnPath(), t);
    }

    return {
      copied: totalCopied,
      updated: totalUpdated,
      skipped: totalSkipped,
      removed: totalRemoved,
    };
  } finally {
    if (!dryRun) {
      releaseLock();
    }
  }
}

// ─── Preferences & State ────────────────────────────────────────────────────

function savePreferences(interviewResult, options = {}) {
  const { dryRun = false, globalDir = GLOBAL_CLAUDE_DIR } = options;
  const prefsPath = path.join(
    globalDir,
    "evolv-coder-kit",
    "user-preferences.json",
  );

  if (dryRun) return;

  let existing = {};
  try {
    existing = JSON.parse(fs.readFileSync(prefsPath, "utf-8"));
  } catch (prefsErr) {
    // Warn when file exists but can't be parsed (corruption vs first-run)
    try {
      if (fs.existsSync(prefsPath)) {
        logWarning(
          `Preferences file could not be read (${prefsErr.message}), using defaults`,
        );
      }
    } catch {
      /* ignore secondary errors */
    }
  }

  const now = new Date().toISOString();
  const prefs = {
    version: require(path.join(PACKAGE_ROOT, "package.json")).version,
    tool: interviewResult.tool || "claude-code",
    outputMode: interviewResult.outputMode || "standard",
    devRigor: interviewResult.devRigor || "standard",
    protections:
      interviewResult.protections || existing.protections || "disable",
    source: interviewResult.source || existing.source || "default",
    contextMonitor: existing.contextMonitor || undefined,
    createdAt: existing.createdAt || now,
    updatedAt: now,
  };

  // Remove undefined keys
  for (const key of Object.keys(prefs)) {
    if (prefs[key] === undefined) delete prefs[key];
  }

  fs.mkdirSync(path.dirname(prefsPath), { recursive: true });
  fs.writeFileSync(prefsPath, JSON.stringify(prefs, null, 2) + "\n", "utf-8");
  logVerbose(`Preferences saved to ${prefsPath}`);
}

function writeRegistry(options = {}) {
  const { dryRun = false } = options;
  const registryPath = path.join(
    GLOBAL_CLAUDE_DIR,
    "evolv-coder-kit",
    "registry.yaml",
  );

  if (fs.existsSync(registryPath)) {
    logVerbose("Registry already exists — skipping");
    return { created: false };
  }

  const scaffold = [
    "# evolv-coder-kit project registry",
    "# Auto-generated — do not edit manually",
    "schema_version: 1",
    "",
    "project_summary:",
    "  total_projects: 0",
    "  active_projects: 0",
    "  last_project_audit: null",
    "  pull_candidates_total: 0",
    "  pull_candidates_pending: 0",
    "  health_distribution:",
    "    A: 0",
    "    B: 0",
    "    C: 0",
    "    D: 0",
    "    F: 0",
    "  health_stats:",
    "    average: null",
    "    median: null",
    "    highest: null",
    "    lowest: null",
    "  by_tracker: {}",
    "  by_platform: {}",
    "  by_template_version: {}",
    "  totals:",
    "    synced: 0",
    "    modified: 0",
    "    outdated: 0",
    "    missing: 0",
    "    custom: 0",
    "  projects_with_deployed_catalog: 0",
    "",
    "projects: {}",
    "",
  ].join("\n");

  if (dryRun) {
    logSuccess("registry.yaml would be created at ~/.claude/evolv-coder-kit/");
    return { created: true };
  }

  fs.mkdirSync(path.dirname(registryPath), { recursive: true });
  const tmpPath = registryPath + ".tmp";
  fs.writeFileSync(tmpPath, scaffold, "utf-8");
  fs.renameSync(tmpPath, registryPath);
  logSuccess(
    "Created project registry at ~/.claude/evolv-coder-kit/registry.yaml",
  );
  return { created: true };
}

function writeGlobalState(options = {}) {
  const { dryRun = false } = options;
  const statePath = path.join(
    GLOBAL_CLAUDE_DIR,
    "evolv-coder-kit",
    "global-state.json",
  );

  if (dryRun) return;

  let existing = {};
  try {
    existing = JSON.parse(fs.readFileSync(statePath, "utf-8"));
  } catch {
    /* first install or corrupted — start fresh */
  }

  const now = new Date().toISOString();
  const state = {
    version: require(path.join(PACKAGE_ROOT, "package.json")).version,
    tool: existing.tool || "claude-code",
    installedAt: existing.installedAt || now,
    lastUpdatedAt: now,
    protectionsEnabled:
      existing.protectionsEnabled !== undefined
        ? existing.protectionsEnabled
        : false,
  };
  // Clean up legacy protectionState field
  delete state.protectionState;

  fs.mkdirSync(path.dirname(statePath), { recursive: true });
  const content = JSON.stringify(state, null, 2) + "\n";
  fs.writeFileSync(statePath + ".tmp", content, "utf-8");
  fs.renameSync(statePath + ".tmp", statePath);
  logVerbose(`Global state saved to ${statePath}`);
}

module.exports = {
  ensureMarketplace,
  syncStandardsRepo,
  installSkills,
  installAssets,
  syncExternal,
  installGlobal,
  runPremiumInstall,
  savePreferences,
  writeRegistry,
  writeGlobalState,
};
