import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { basename, extname, join, resolve } from "node:path";
import { createInterface } from "node:readline";
import { verifyArtifactExists } from "../artifacts-exist.ts";
import { parseWorkflowDefinitions } from "../workflow.ts";
import { KxmConfigError, KxmSchemaRegistry, parseRestrictedYaml } from "../project-config.ts";
import { compileKxmWorkflow } from "../engine-compile.ts";
import { redactSecrets } from "../redact.ts";
import {
  telemetryPath,
  readRoutingRecords,
} from "../telemetry.ts";
import {
  behavioralConfigHash,
  compareRoutingRecords,
  groupByBehavior,
  generateRoutingReport,
  formatRoutingReport,
  type RoutingRecord,
  type RoutingRecordV2,
} from "../routing.ts";
import { acknowledgePriceCatalog, loadPriceCatalog, type PriceCatalog } from "../prices.ts";
import {
  buildImprovementReport,
  formatImprovementReport,
  writeImprovementReport,
} from "../improve.ts";
import {
  loadRoutingSources,
  type LoadedRoutingSources,
  type RoutingSourceSummary,
} from "../improve-sources.ts";
import {
  loadModesConfig,
  resolveActiveMode,
  calculatePromptFootprint,
  formatModesExplainReport,
} from "../modes.ts";
import {
  executeSshRun,
  closeControlSocket,
} from "../ssh-remote.ts";
import {
  loadKxmConfig,
  setKxmConfigValue,
  getKxmConfigValue,
  formatKxmConfig,
  userConfigDirForEnv,
} from "../config.ts";
import { runKxmConfigEditor } from "../config-editor.ts";
import { generateShellCompletion, type SupportedShell } from "../autocomplete.ts";
import {
  completionRcTarget,
  completionScriptPath,
  detectShell,
  installPathEntry,
  installShellCompletion,
  kxmBinDir,
} from "../completion-install.ts";
import {
  GUIDE_WORKFLOWS,
  parseGuideSelection,
  mergeGuideRouteAdmission,
  planGuideSetup,
  renderGuideSetupFiles,
  writeGuideSetupFiles,
} from "../init-guide-setup.ts";
import {
  classifyInstallRoot,
  kxmSourceCheckoutRoot,
  resolveInstallKind,
  type InstallKindReport,
  type InstallProbe,
} from "../kxm-install-kind.ts";
import {
  installedKxmVersion,
  fetchLatestKxmVersion,
  ghReleaseDownloadAvailable,
  kxmReleaseAssetName,
  noticeFromVersions,
  planKxmPackageUpdate,
  readInstalledKxmVersion,
  readUpdateCache,
  verifyReleaseAssetDigest,
  writeUpdateCache,
  KxmUpdateConfigError,
  type KxmPackageUpdateStep,
  type KxmUpdateConfig,
  type KxmUpdateNotice,
} from "../kxm-update.ts";
import { loadKxmUpdateConfig } from "../kxm-update-config.ts";
import {
  formatHarnessUpdate,
  planHarnessUpdate,
  probeHarnessesAsync,
  runHarnessUpdate,
  type HarnessUpdateScope,
} from "../harness.ts";
import { kxmUserStateRoot } from "../bindings.ts";
import {
  print,
  printPlan,
  printWorker,
  gateOf,
  type CliIo,
  type CliSpawnResult,
  type Runtime,
} from "./types.ts";
import {
  installProbeFrom,
  warnIgnoredProjectUpdateYaml,
  refreshKxmUpdateNotice,
} from "./hub.ts";
import { findKxmRepoRoot } from "../repo-root.ts";


export function cliSpawn(runtime: Runtime, command: string, args: readonly string[], extra?: { timeout?: number }): CliSpawnResult {
  if (runtime.io.spawnSync) return runtime.io.spawnSync(command, args);
  const result = spawnSync(command, [...args], {
    encoding: "utf8",
    windowsHide: true,
    shell: process.platform === "win32",
    ...extra,
  });
  return {
    status: result.status,
    stdout: typeof result.stdout === "string" ? result.stdout : "",
    stderr: typeof result.stderr === "string" ? result.stderr : "",
    ...(result.error ? { error: result.error } : {}),
  };
}

export function npmGlobalRootFn(runtime: Runtime): () => string | undefined {
  return () => {
    const result = cliSpawn(runtime, "npm", ["root", "-g"], { timeout: 5_000 });
    if (result.error || result.status !== 0) return undefined;
    const out = result.stdout.trim();
    return out || undefined;
  };
}

export function installKindPayload(report: InstallKindReport): { installKind: string; root: string } {
  return { installKind: report.kind, root: report.root };
}

export function formatPackageUpdateStep(step: KxmPackageUpdateStep): string {
  if (step.kind === "verify") return `verify sha256 ${step.path}`;
  return `${step.command} ${step.args.join(" ")}`;
}

export function applyKxmPackageUpdate(runtime: Runtime, notice: KxmUpdateNotice): { ok: boolean; detail: string; error?: string } {
  if (!notice.latest) return { ok: false, detail: "no_latest_version" };
  if (notice.source === "github" && !notice.asset?.sha256) {
    const name = kxmReleaseAssetName(notice.latest);
    return {
      ok: false,
      error: "release_digest_missing",
      detail: `release v${notice.latest} has no sha256 digest for ${name}; refusing to install`,
    };
  }
  const releaseDir = mkdtempSync(join(tmpdir(), "kxm-pkg-update-"));
  try {
    const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
    const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
    if (runtime.dryRun) {
      return { ok: true, detail: planned.map(formatPackageUpdateStep).join(" && ") };
    }
    for (const step of planned) {
      if (step.kind === "verify") {
        if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
          const actual = existsSync(step.path)
            ? createHash("sha256").update(readFileSync(step.path)).digest("hex")
            : "missing";
          return {
            ok: false,
            error: "release_digest_mismatch",
            detail: `${kxmReleaseAssetName(notice.latest)} sha256 ${actual} does not match release digest ${step.sha256}; refusing to install`,
          };
        }
        continue;
      }
      const result = cliSpawn(runtime, step.command, step.args);
      if (result.status !== 0) {
        const detail = (result.stderr || result.stdout || result.error?.message || "update_failed").trim().slice(0, 500);
        return { ok: false, detail };
      }
    }
    return { ok: true, detail: `installed ${notice.latest} from ${notice.source}` };
  } finally {
    rmSync(releaseDir, { recursive: true, force: true });
  }
}

export async function cmdExplain(
  runtime: Runtime,
  options: { mode?: string | undefined; domains?: string | undefined; model?: string | undefined },
): Promise<number> {
  const modesConfig = loadModesConfig(runtime.dirs.workdir);
  const majorMode = options.mode || "coder";
  const domains = options.domains
    ? options.domains.split(",").map((s) => s.trim()).filter(Boolean)
    : [];
  const resolved = resolveActiveMode(modesConfig, majorMode, domains);
  if (options.model) {
    resolved.model = options.model;
  }
  const footprint = calculatePromptFootprint(resolved, runtime.dirs.workdir);
  if (runtime.json) {
    print(runtime.io, runtime.json, { ok: true, command: "explain", ...footprint }, "");
  } else {
    runtime.io.stdout(formatModesExplainReport(footprint) + "\n");
  }
  return 0;
}

export async function cmdSshInfo(runtime: Runtime, host?: string | undefined): Promise<number> {
  const receipt = executeSshRun({ action: "info", host });
  if (runtime.json) {
    print(runtime.io, runtime.json, receipt, "");
  } else {
    if (receipt.hosts && receipt.hosts.length > 0) {
      runtime.io.stdout("Configured SSH Hosts:\n");
      for (const h of receipt.hosts) {
        runtime.io.stdout(`  • ${h.alias} (${h.user ? `${h.user}@` : ""}${h.hostName || "unresolved"}:${h.port || 22})\n`);
      }
    } else {
      runtime.io.stdout("No configured SSH host aliases found in ~/.ssh/config\n");
    }
  }
  return receipt.ok ? 0 : 1;
}

export async function cmdSshRun(
  runtime: Runtime,
  host: string,
  commandParts: string[],
  options: { sudo?: boolean | undefined },
): Promise<number> {
  const command = commandParts.join(" ");
  if (runtime.dryRun) {
    printPlan(runtime, { command: "ssh run", host, remoteCommand: command, sudo: options.sudo === true }, [{ action: "ssh", target: `${host}: ${options.sudo ? "sudo " : ""}${command}` }], `run a command on ${host}`);
    return 0;
  }
  const receipt = executeSshRun({
    action: "command",
    host,
    command,
    sudo: options.sudo,
  });
  if (runtime.json) {
    print(runtime.io, runtime.json, receipt, "");
  } else {
    if (receipt.stdout) runtime.io.stdout(receipt.stdout + "\n");
    if (receipt.stderr) runtime.io.stderr(receipt.stderr + "\n");
    if (!receipt.ok && receipt.error) runtime.io.stderr(`Error: ${receipt.error}\n`);
  }
  return receipt.ok ? 0 : (receipt.exitCode || 1);
}

export async function cmdSshFile(
  runtime: Runtime,
  host: string,
  filePath: string,
  options: { content?: string | undefined; read?: boolean | undefined; append?: boolean | undefined; sudo?: boolean | undefined },
): Promise<number> {
  const op = options.read ? "read" : (options.append ? "append" : "write");
  if (runtime.dryRun) {
    printPlan(runtime, { command: "ssh file", host, path: filePath, op, sudo: options.sudo === true }, [{ action: "ssh", target: `${host}: ${op} ${filePath}` }], `${op} ${filePath} on ${host}`);
    return 0;
  }
  const receipt = executeSshRun({
    action: "file",
    host,
    file_path: filePath,
    file_content: options.content,
    file_op: op,
    sudo: options.sudo,
  });
  if (runtime.json) {
    print(runtime.io, runtime.json, receipt, "");
  } else {
    if (receipt.stdout) runtime.io.stdout(receipt.stdout + "\n");
    if (receipt.stderr) runtime.io.stderr(receipt.stderr + "\n");
    if (receipt.ok && !options.read) {
      runtime.io.stdout(`Successfully wrote ${receipt.bytesProcessed ?? 0} bytes to ${filePath} on ${host}\n`);
    }
    if (!receipt.ok && receipt.error) runtime.io.stderr(`Error: ${receipt.error}\n`);
  }
  return receipt.ok ? 0 : (receipt.exitCode || 1);
}

export async function cmdSshClose(runtime: Runtime, host: string): Promise<number> {
  if (runtime.dryRun) {
    printPlan(runtime, { command: "ssh close", host }, [{ action: "ssh", target: `${host}: close the ControlMaster socket` }], `close the ControlMaster socket for ${host}`);
    return 0;
  }
  const closed = closeControlSocket(host);
  if (runtime.json) {
    print(runtime.io, runtime.json, { ok: true, closed, command: "ssh close", host }, "");
  } else {
    runtime.io.stdout(closed ? `Closed ControlMaster socket for ${host}\n` : `No active ControlMaster socket found for ${host}\n`);
  }
  return 0;
}

export async function cmdUpdate(runtime: Runtime, harness: string | undefined, options: {
  self?: boolean | undefined;
  extensions?: boolean | undefined;
  models?: boolean | undefined;
  check?: boolean | undefined;
  kxm?: boolean | undefined;
  all?: boolean | undefined;
}): Promise<number> {
  if (options.check && (options.kxm || options.self || options.extensions || options.models || options.all || harness)) {
    print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "--check cannot be combined with other update flags");
    return 2;
  }
  const selected = [options.self, options.extensions, options.models, options.all].filter(Boolean).length;
  if (selected > 1) {
    print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --all, --self, --extensions, or --models");
    return 2;
  }
  warnIgnoredProjectUpdateYaml(runtime);
  const probe = installProbeFrom(runtime);
  const classified = classifyInstallRoot(probe);
  // The version of the install being updated, taken from the root that was just
  // classified — not from whichever directory the operator stood in. A
  // `readInstalledKxmVersion(resolve("."))` in the notice path turned
  // `cd ~ && kxm update --kxm` into an uncaught ENOENT on `$HOME/package.json`,
  // and would happily compare a stranger project's version wherever one did
  // exist. Unreadable is now a stated refusal.
  // Exception: a cwd that *is* this repository (`@kontextmind/kxm` plus `.git`)
  // is a local-files apply, not a stranger version compare.
  const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
  const localRoot = kxmSourceCheckoutRoot(runtime.dirs.workdir) ?? kxmSourceCheckoutRoot(runtime.cwd);
  let notice: KxmUpdateNotice;
  let kindReport = classified;
  if (localRoot) {
    const localVersion = installedKxmVersion(localRoot) ?? current ?? "unknown";
    notice = {
      current: current ?? localVersion,
      latest: localVersion,
      available: true,
      auto: false,
      source: "local",
      message: `kxm ${current ?? localVersion} (local ${localRoot})`,
      localRoot,
    };
  } else if (classified.kind === "source") {
    if (options.check) {
      const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
      print(runtime.io, runtime.json, {
        ok: true,
        command: "update check",
        current: current ?? "unknown",
        available: false,
        auto: false,
        source: "npm",
        installKind: "source",
        root: classified.root,
        message,
      }, message);
      return 0;
    }
    if (options.kxm) {
      print(runtime.io, runtime.json, {
        ok: false,
        command: "update",
        error: "install_kind_source",
        installKind: "source",
        root: classified.root,
        instruction: classified.instruction,
      }, classified.instruction);
      return 2;
    }
    notice = {
      current: current ?? "unknown",
      available: false,
      auto: false,
      source: "npm",
      message: `kxm ${current ?? "unknown"} (running from source)`,
    };
  } else {
    try {
      notice = await refreshKxmUpdateNotice(runtime, undefined, current);
    } catch (error) {
      if (error instanceof KxmUpdateConfigError) {
        print(runtime.io, runtime.json, { ok: false, command: "update", error: error.code, ...installKindPayload(classified) }, error.message);
        return 2;
      }
      throw error;
    }
  }
  if (options.check) {
    print(runtime.io, runtime.json, {
      ok: true,
      command: "update check",
      ...notice,
      ...installKindPayload(classified),
    }, notice.message);
    return 0;
  }
  const fullUpdate = Boolean(options.all) || (selected === 0 && !options.kxm && !harness);
  const applyKxm = Boolean(options.kxm || fullUpdate || notice.auto);
  let kxmApply: { ok: boolean; detail: string; error?: string } | undefined;
  const applyKxmPackage = (requireSuccess: boolean): number | undefined => {
    if (!applyKxm) return undefined;
    if (notice.source === "local" && notice.localRoot) {
      kxmApply = applyKxmPackageUpdate(runtime, notice);
      if (!kxmApply.ok && requireSuccess) {
        print(runtime.io, runtime.json, {
          ok: false,
          command: "update",
          error: kxmApply.error,
          kxm: kxmApply,
          notice,
          ...installKindPayload(classified),
        }, kxmApply.detail);
        return 1;
      }
      return undefined;
    }
    const resolved = resolveInstallKind(probe, npmGlobalRootFn(runtime));
    kindReport = resolved;
    if (resolved.kind !== "npm-global") {
      if (requireSuccess) {
        print(runtime.io, runtime.json, {
          ok: false,
          command: "update",
          error: `install_kind_${resolved.kind}`,
          installKind: resolved.kind,
          root: resolved.root,
          instruction: resolved.instruction,
          notice,
        }, resolved.instruction);
        return 2;
      }
      if (notice.available) runtime.io.stderr(`kxm: ${resolved.instruction}\n`);
      return undefined;
    }
    if (notice.available) {
      kxmApply = applyKxmPackageUpdate(runtime, notice);
      if (!kxmApply.ok && requireSuccess) {
        print(runtime.io, runtime.json, {
          ok: false,
          command: "update",
          error: kxmApply.error,
          kxm: kxmApply,
          notice,
          ...installKindPayload(resolved),
        }, kxmApply.detail);
        return 1;
      }
    }
    return undefined;
  };
  const skipHarness = Boolean(options.kxm && !fullUpdate && selected === 0 && !harness && !notice.auto);
  if (skipHarness) {
    const aborted = applyKxmPackage(true);
    if (aborted !== undefined) return aborted;
    print(
      runtime.io,
      runtime.json,
      { ok: kxmApply?.ok !== false, command: "update", dryRun: runtime.dryRun, notice, kxm: kxmApply, ...installKindPayload(kindReport) },
      kxmApply?.detail ?? notice.message,
    );
    return kxmApply?.ok === false ? 1 : 0;
  }
  const scope: HarnessUpdateScope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
  const inventory = await probeHarnessesAsync({ env: runtime.env });
  const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope: fullUpdate ? "all" : scope });
  const runOpts = { env: runtime.env, dryRun: runtime.dryRun };
  let steps;
  if (fullUpdate) {
    const selfSteps = runHarnessUpdate(planned.filter((step) => step.scope === "self"), runOpts);
    applyKxmPackage(false);
    const restSteps = runHarnessUpdate(planned.filter((step) => step.scope !== "self"), runOpts);
    steps = [...selfSteps, ...restSteps];
  } else {
    const aborted = applyKxmPackage(Boolean(options.kxm));
    if (aborted !== undefined) return aborted;
    steps = runHarnessUpdate(planned, runOpts);
  }
  const kxmFailed = kxmApply?.ok === false;
  const skippedUnknown = steps.some((step) => step.detail === "unknown_harness");
  const harnessFailed = steps.some((step) => step.outcome === "failed");
  const text = [notice.available ? notice.message : undefined, kxmApply?.detail, formatHarnessUpdate(steps)].filter(Boolean).join("\n");
  print(runtime.io, runtime.json, {
    ok: !kxmFailed && !skippedUnknown,
    command: "update",
    dryRun: runtime.dryRun,
    scope: fullUpdate ? "all" : scope,
    defaultHarness: inventory.defaultHarness,
    notice,
    ...(kxmApply ? { kxm: kxmApply } : {}),
    steps,
    ...(harnessFailed ? { degraded: true } : {}),
    ...installKindPayload(kindReport),
  }, text);
  if (skippedUnknown) return 2;
  if (kxmFailed) return 1;
  if (harnessFailed && !fullUpdate) return 1;
  return 0;
}

export async function cmdValidate(runtime: Runtime, fileFlag?: string | undefined): Promise<number> {
  const worker = gateOf(runtime, "validate");
  const explicitFile = fileFlag?.trim();
  const configuredFile = runtime.env.KXM_WEBHOOK_WORKFLOWS_FILE?.trim();
  const inline = runtime.env.KXM_WEBHOOK_WORKFLOWS?.trim();
  if (!explicitFile && configuredFile && inline) {
    printWorker(
      runtime,
      worker,
      { ok: false, command: "validate", error: "ambiguous_workflow_source" },
      "configure only one of KXM_WEBHOOK_WORKFLOWS or KXM_WEBHOOK_WORKFLOWS_FILE",
    );
    return 2;
  }
  if (!explicitFile && !configuredFile && !inline) {
    printWorker(
      runtime,
      worker,
      { ok: false, command: "validate", error: "workflow_source_required" },
      "provide --file or configure exactly one workflow source environment variable",
    );
    return 2;
  }

  const selectedFile = explicitFile || configuredFile;
  const file = selectedFile ? resolve(runtime.cwd, selectedFile) : undefined;
  if (file && !existsSync(file)) {
    printWorker(runtime, worker, { ok: false, command: "validate", error: "file_not_found", file }, `workflow file not found: ${file}`);
    return 1;
  }
  try {
    const raw = file ? readFileSync(file, "utf8") : inline!;
    // Local workflow mappings use the runner's restricted YAML/schema/compiler.
    // Webhook sources remain JSON arrays with their existing secret checks.
    if (explicitFile && file && !raw.trimStart().startsWith("[")) {
      const value = parseRestrictedYaml(raw, file);
      const issues = new KxmSchemaRegistry().validate("workflow", value, file);
      if (issues.length > 0) throw new KxmConfigError(issues);
      const id = basename(file, extname(file));
      compileKxmWorkflow({ id, value, logicalPath: file });
      printWorker(
        runtime,
        worker,
        { ok: true, command: "validate", source: "file", file, workflows: [{ id, schema: value.schema }], warnings: [] },
        `validated local workflow ${id} (schema and transitions); kxm init validates project references`,
      );
      return 0;
    }
    const warnings: string[] = [];
    const definitions = parseWorkflowDefinitions(raw, runtime.env, (message) => warnings.push(message));
    const secretEnvs = definitions.map((definition) => ({
      id: definition.id,
      secretConfigured: Boolean(definition.secret),
      signalSecretConfigured: Boolean(definition.signalSecret),
    }));
    const source = file ? "file" : "inline";
    printWorker(
      runtime,
      worker,
      { ok: true, command: "validate", source, ...(file ? { file } : {}), workflows: secretEnvs, warnings },
      `validated ${definitions.length} workflow(s) from ${source}${warnings.length ? ` with ${warnings.length} warning(s)` : ""}`,
      warnings.length ? "warning" : undefined,
    );
    return 0;
  } catch (error) {
    const message = error instanceof Error ? redactSecrets(error.message) : "invalid_workflow";
    printWorker(runtime, worker, { ok: false, command: "validate", error: message }, message);
    return 1;
  }
}

export async function cmdArtifactsExist(runtime: Runtime, pathFlag: string): Promise<number> {
  const worker = gateOf(runtime, "artifacts-exist");
  const checked = verifyArtifactExists(runtime.dirs.assets, resolve(runtime.cwd, pathFlag));
  if (!checked.ok) {
    printWorker(
      runtime,
      worker,
      { ok: false, command: "artifacts-exist", error: checked.error, path: checked.path },
      `artifact verification failed: ${checked.error}`,
    );
    return 1;
  }
  printWorker(
    runtime,
    worker,
    { ok: true, command: "artifacts-exist", path: checked.path, bytes: checked.bytes },
    "artifact exists and is non-empty under workspace assets",
  );
  return 0;
}

const IMPROVE_SOURCE_UNREADABLE = "improve_source_unreadable";

/** Load routing sources, or print the unreadable-source failure (exit 1). */
function loadRoutingSourcesOrReport(runtime: Runtime, command: string, file?: string | undefined): LoadedRoutingSources | undefined {
  try {
    return loadRoutingSources({
      cwd: runtime.cwd,
      env: runtime.env,
      logsDir: runtime.dirs.logs,
      ...(file !== undefined ? { file } : {}),
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    const detail = message.startsWith(`${IMPROVE_SOURCE_UNREADABLE}: `) ? message.slice(IMPROVE_SOURCE_UNREADABLE.length + 2) : message;
    print(
      runtime.io,
      runtime.json,
      { ok: false, command, error: IMPROVE_SOURCE_UNREADABLE, detail },
      `${IMPROVE_SOURCE_UNREADABLE}: ${detail}`,
    );
    return undefined;
  }
}

function formatRoutingSource(source: RoutingSourceSummary): string {
  const counts = (["records", "skippedInvalid", "excludedSimulated", "undecided", "duplicatesDropped"] as const)
    .filter((key) => source[key] !== undefined)
    .map((key) => `${key}=${source[key]}`);
  return `  ${source.kind.padEnd(9)} ${source.path} (exists=${source.exists}, ${counts.join(", ")})`;
}

export async function cmdImprove(runtime: Runtime, options: { file?: string | undefined; outDir?: string | undefined } = {}): Promise<number> {
  const loaded = loadRoutingSourcesOrReport(runtime, "improve", options.file);
  if (!loaded) return 1;
  const root = loaded.projectRoot ?? runtime.cwd;

  let config: ReturnType<typeof loadKxmConfig>;
  try {
    const userConfigDir = runtime.env.KXM_USER_CONFIG_DIR?.trim();
    config = loadKxmConfig(root, userConfigDir ? { userConfigDir } : {});
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    print(runtime.io, runtime.json, { ok: false, command: "improve", error: "config_invalid", detail: message }, `config_invalid: ${message}`);
    return 1;
  }

  const candidatesDir = options.outDir
    ? resolve(runtime.cwd, options.outDir)
    : join(root, ".kxm", "candidates");

  const report = buildImprovementReport(loaded.records, {
    candidatesDir,
    projectRoot: root,
    dryRun: runtime.dryRun,
    promotionPolicy: config.improvement.promotionPolicy,
    autoThreshold: config.improvement.autoThreshold,
    halfLifeDays: config.improvement.telemetryHalfLifeDays,
    routeSwitches: loaded.routeSwitches,
  });

  const reportDir = join(runtime.dirs.assets, "improvements");
  const reportPath = writeImprovementReport(reportDir, report, runtime.dryRun);

  const text = [
    "Sources:",
    ...loaded.sources.map(formatRoutingSource),
    loaded.projectRoot !== undefined
      ? `Project root: ${loaded.projectRoot}`
      : `Runtime store not read: no KXM project at ${runtime.cwd}`,
    "",
    formatImprovementReport(report),
  ].join("\n");
  print(runtime.io, runtime.json, {
    ok: true,
    command: "improve",
    dryRun: runtime.dryRun || undefined,
    path: reportPath,
    events: report.recordsCount,
    recordsCount: report.recordsCount,
    groupsCount: report.groups.length,
    candidatesCount: report.candidates.length,
    candidates: report.candidates,
    report,
    sources: loaded.sources,
    projectRoot: loaded.projectRoot ?? null,
  }, text);
  return 0;
}

export async function cmdConfigGet(runtime: Runtime, key: string): Promise<number> {
  try {
    const config = loadKxmConfig(runtime.cwd);
    const value = getKxmConfigValue(config, key);
    print(
      runtime.io,
      runtime.json,
      { ok: true, command: "config get", key, value },
      value !== undefined ? String(value) : "(undefined)",
    );
    return 0;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    runtime.io.stderr(`config get failed: ${message}\n`);
    return 1;
  }
}

export async function cmdConfigSet(
  runtime: Runtime,
  key: string,
  value: string,
  options: { scope: string },
): Promise<number> {
  try {
    const scope = options.scope === "user" ? "user" : "project";
    let parsedVal: unknown = value;
    try {
      parsedVal = JSON.parse(value);
    } catch {
      // keep string
    }
    const { file } = setKxmConfigValue(runtime.cwd, key, parsedVal, { scope, dryRun: runtime.dryRun });
    if (runtime.dryRun) {
      printPlan(runtime, { command: "config set", key, value: parsedVal, scope }, [{ action: "write", target: file }], `set ${key} = ${value} in ${scope} config`);
      return 0;
    }
    print(
      runtime.io,
      runtime.json,
      { ok: true, command: "config set", key, value: parsedVal, scope },
      `Set ${key} = ${value} in ${scope} config`,
    );
    return 0;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    runtime.io.stderr(`config set failed: ${message}\n`);
    return 1;
  }
}

export async function cmdConfigEdit(
  runtime: Runtime,
  options: { scope?: string },
  isTty = Boolean(process.stdin.isTTY && process.stdout.isTTY),
): Promise<number> {
  const scope = options.scope;
  if (scope !== "project" && scope !== "user") {
    const detail = "config edit requires --scope project or --scope user";
    print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
    return 2;
  }
  if (runtime.json) {
    const detail = "config edit does not print machine-readable output";
    print(runtime.io, runtime.json, { ok: false, command: "config edit", error: "config_edit_unsupported", detail }, detail);
    return 2;
  }
  try {
    const userConfigDir = userConfigDirForEnv(runtime.env);
    return await runKxmConfigEditor({
      target: {
        cwd: runtime.cwd,
        scope,
        ...(userConfigDir === undefined ? {} : { userConfigDir }),
      },
      stdout: (text) => runtime.io.stdout(text),
      isTty,
      dryRun: runtime.dryRun,
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    runtime.io.stderr(`config edit failed: ${message}\n`);
    return 1;
  }
}

export async function cmdConfigList(runtime: Runtime): Promise<number> {
  try {
    const config = loadKxmConfig(runtime.cwd);
    const text = formatKxmConfig(config);
    print(runtime.io, runtime.json, { ok: true, command: "config list", config }, text);
    return 0;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    runtime.io.stderr(`config list failed: ${message}\n`);
    return 1;
  }
}

export async function cmdCompletion(runtime: Runtime, shell: string): Promise<number> {
  try {
    if (shell !== "bash" && shell !== "zsh" && shell !== "fish") {
      runtime.io.stderr(`unsupported shell: ${shell}; must be bash, zsh, or fish\n`);
      return 1;
    }
    const script = generateShellCompletion(shell as SupportedShell);
    runtime.io.stdout(script);
    return 0;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    runtime.io.stderr(`completion generation failed: ${message}\n`);
    return 1;
  }
}

export async function cmdCompletionInstall(
  runtime: Runtime,
  options: { shell?: string | undefined; path?: boolean | undefined },
): Promise<number> {
  const installOptions = {
    env: runtime.env,
    configDir: runtime.env.KXM_USER_CONFIG_DIR,
    platform: process.platform as NodeJS.Platform,
    dryRun: runtime.dryRun,
  };
  const report = installShellCompletion(options.shell, installOptions);
  const pathReport = options.path === false ? undefined : installPathEntry(options.shell, installOptions);
  if (!report.ok) {
    print(runtime.io, runtime.json, {
      ok: false,
      command: "completion install",
      error: report.reason ?? "shell_not_detected",
    }, "could not detect your shell; pass --shell bash, zsh, or fish");
    return 1;
  }
  const shellNote = report.shell;
  const applyNote = runtime.dryRun
    ? "planned; rerun without --dry-run to apply"
    : report.alreadyInstalled
      ? "already installed"
      : "installed; restart your shell or open a new terminal to activate";
  const pathNote = pathReport
    ? pathReport.ok
      ? runtime.dryRun
        ? `; PATH entry for ${pathReport.binDir} planned`
        : pathReport.alreadyInstalled
          ? "; kxm already on PATH"
          : `; PATH entry for ${pathReport.binDir} added to ${pathReport.rcFile}`
      : undefined
    : undefined;
  print(runtime.io, runtime.json, {
    ok: true,
    command: "completion install",
    shell: shellNote,
    scriptPath: report.scriptPath,
    ...(report.rcFile ? { rcFile: report.rcFile } : {}),
    rcModified: report.rcModified,
    alreadyInstalled: report.alreadyInstalled,
    ...(pathReport ? { path: pathReport } : {}),
    dryRun: runtime.dryRun === true,
  }, `kxm ${shellNote} completion: ${applyNote}${report.rcFile ? ` (rc: ${report.rcFile})` : ""}${pathNote ?? ""}`);
  return 0;
}

export async function maybeOfferCompletionInstall(runtime: Runtime): Promise<void> {
  if (runtime.json || runtime.dryRun) return;
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
  if (runtime.env.KXM_SKIP_COMPLETION_PROMPT?.trim()) return;
  const shell = detectShell(runtime.env, process.platform);
  if (shell !== "bash" && shell !== "zsh" && shell !== "fish") return;
  const binDir = kxmBinDir(runtime.env);
  const pathNeeded = shell !== "fish" && binDir && !(runtime.env.PATH ?? "").split(":").includes(binDir);
  if (!pathNeeded) {
    const scriptPath = completionScriptPath(shell, { env: runtime.env, configDir: runtime.env.KXM_USER_CONFIG_DIR });
    const { rcFile } = completionRcTarget(shell, { env: runtime.env });
    if (rcFile && existsSync(rcFile)) {
      try {
        if (readFileSync(rcFile, "utf8").includes(scriptPath)) return;
      } catch {
        // unreadable rc: still offer
      }
    }
  }
  const question = pathNeeded
    ? `\nInstall ${shell} tab completion for kxm and add ${binDir} to PATH? [Y/n] `
    : `\nInstall ${shell} tab completion for kxm? [y/N] `;
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  await new Promise<void>((resolvePrompt) => {
    rl.question(question, (answer) => {
      rl.close();
      const trimmed = answer.trim().toLowerCase();
      const accept = pathNeeded ? trimmed !== "n" && trimmed !== "no" : trimmed === "y" || trimmed === "yes";
      if (accept) {
        try {
          const report = installShellCompletion(shell, {
            env: runtime.env,
            configDir: runtime.env.KXM_USER_CONFIG_DIR,
            platform: process.platform,
          });
          if (report.ok) {
            runtime.io.stdout(`completion installed for ${report.shell}`);
          } else {
            runtime.io.stdout(`completion install skipped: ${report.reason ?? "unknown"}\n`);
            resolvePrompt();
            return;
          }
        } catch {
          runtime.io.stdout("completion install skipped: local filesystem operation did not complete\n");
          resolvePrompt();
          return;
        }
        if (pathNeeded && binDir) {
          try {
            const pathReport = installPathEntry(shell, {
              env: runtime.env,
              configDir: runtime.env.KXM_USER_CONFIG_DIR,
              platform: process.platform,
              binDir,
            });
            runtime.io.stdout(pathReport.ok && pathReport.rcModified
              ? `; ${binDir} added to PATH in ${pathReport.rcFile}`
              : "; kxm already on PATH");
          } catch {
            runtime.io.stdout("; PATH entry skipped: local filesystem operation did not complete");
          }
        }
        runtime.io.stdout("; restart your shell or open a new terminal to activate\n");
      } else {
        runtime.io.stdout("skipped; run `kxm completion install` anytime\n");
      }
      resolvePrompt();
    });
  });
}

const GUIDE_SETUP_OPT_OUT_ENV = "KXM_SKIP_GUIDE_SETUP_PROMPT";

async function askYesNo(question: string): Promise<boolean> {
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  return new Promise<boolean>((resolvePrompt) => {
    rl.question(question, (answer) => {
      rl.close();
      const trimmed = answer.trim().toLowerCase();
      resolvePrompt(trimmed === "y" || trimmed === "yes");
    });
  });
}

async function askLine(question: string): Promise<string> {
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  return new Promise<string>((resolvePrompt) => {
    rl.question(question, (answer) => {
      rl.close();
      resolvePrompt(answer);
    });
  });
}

export async function maybeOfferGuideSetup(runtime: Runtime): Promise<void> {
  if (runtime.json || runtime.dryRun) return;
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
  if (runtime.env[GUIDE_SETUP_OPT_OUT_ENV]?.trim()) return;
  let inventory;
  try {
    inventory = await probeHarnessesAsync({ env: runtime.env });
  } catch {
    return;
  }
  const authenticated = inventory.harnesses.filter((entry) => entry.detected && entry.authenticated === true);
  if (authenticated.length === 0) {
    runtime.io.stdout("no authenticated harnesses detected; skipping workflow-guide setup (see `kxm harness list`)\n");
    return;
  }
  const harnessList = authenticated.map((entry) => entry.id).join(", ");
  const accept = await askYesNo(`\nSet up workflow-guide agents and workflows for authenticated harnesses (${harnessList})? [y/N] `);
  if (!accept) {
    runtime.io.stdout(`skipped; set ${GUIDE_SETUP_OPT_OUT_ENV}=1 to suppress this offer, or re-run on a fresh project\n`);
    return;
  }
  const lines = ["", "Workflow-guide software-engineering workflows (docs/reference/workflow-catalog.md):"];
  GUIDE_WORKFLOWS.forEach((workflow, index) => {
    lines.push(`  ${index + 1}) ${workflow.slug.padEnd(32)} ${workflow.summary}`);
  });
  runtime.io.stdout(`${lines.join("\n")}\n`);
  const answer = await askLine("Install which workflows? (numbers or slugs, comma-separated, 'all', or 'none'): ");
  const selected = parseGuideSelection(answer);
  if (selected.length === 0) {
    runtime.io.stdout("no workflows selected; nothing written\n");
    return;
  }
  const plan = planGuideSetup({ inventory, selected });
  const files = renderGuideSetupFiles(runtime.cwd, plan);
  const report = writeGuideSetupFiles(files);
  const admitted = mergeGuideRouteAdmission(runtime.cwd, plan);
  for (const file of report.written) runtime.io.stdout(`wrote ${file}\n`);
  for (const file of report.existed) runtime.io.stdout(`kept existing ${file} (not overwritten)\n`);
  for (const selector of admitted) runtime.io.stdout(`admitted route ${selector}\n`);
  for (const skip of plan.skipped) {
    runtime.io.stdout(`skipped ${skip.workflow}/${skip.role}: ${skip.reason}\n`);
  }
  if (report.written.length > 0) {
    runtime.io.stdout("inspect with `kxm workflow definitions`; guide candidates are dated research — verify before dispatch\n");
  }
}

export async function cmdPricesAcknowledge(runtime: Runtime): Promise<number> {
  try {
    const catalog = acknowledgePriceCatalog(runtime.cwd);
    print(runtime.io, runtime.json, {
      ok: true,
      command: "prices acknowledge",
      date: catalog.date,
      sha256: catalog.sha256,
      note: "stamped the existing list as today's estimate; vendor rates were not fetched",
    }, `price catalog stamped ${catalog.date} (list estimate only; vendor rates were not fetched)`);
    return 0;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    print(runtime.io, runtime.json, { ok: false, command: "prices acknowledge", error: "prices_acknowledge_failed", message }, `prices acknowledge failed: ${message}`);
    return 1;
  }
}

export async function cmdRoutingReport(
  runtime: Runtime,
  options: { file?: string | undefined; equivalentListCost?: boolean | undefined; listPrices?: boolean | undefined; prices?: string | undefined },
): Promise<number> {
  let file: string;
  let records: Array<RoutingRecord | RoutingRecordV2>;
  let sources: RoutingSourceSummary[] | undefined;
  let routeSwitches: LoadedRoutingSources["routeSwitches"] = [];
  if (options.file !== undefined) {
    file = options.file;
    records = readRoutingRecords(file).map((entry) => entry.routing);
  } else {
    // The project's Runtime store (when cwd is in a KXM project), then telemetry.
    const loaded = loadRoutingSourcesOrReport(runtime, "routing report");
    if (!loaded) return 1;
    file = telemetryPath(runtime.dirs.logs);
    records = loaded.records;
    sources = loaded.sources;
    routeSwitches = loaded.routeSwitches;
  }
  const includeEquivalentListCost = Boolean(options.equivalentListCost || options.listPrices);

  let catalog: PriceCatalog | undefined;
  if (includeEquivalentListCost) {
    try {
      const pricesPath = options.prices ? resolve(runtime.cwd, options.prices) : join(runtime.dirs.workspace, "prices.yaml");
      catalog = loadPriceCatalog(pricesPath);
    } catch {
      // price catalog optional / best effort
    }
  }

  const report = generateRoutingReport(records, { catalog, includeEquivalentListCost, routeSwitches });

  if (records.length === 0) {
    print(runtime.io, runtime.json, { ok: true, command: "routing report", file, ...(sources ? { sources } : {}), configurations: [], report }, "no routing records in telemetry");
    return 0;
  }

  const v1Records = records.filter((r) => r.schema === "kxm.routing-record.v1") as any[];
  const configurations = v1Records.length > 0
    ? [...groupByBehavior(v1Records).entries()]
        .map(([hash, group]) => ({ ...compareRoutingRecords(group), behavioralSha256: hash }))
        .sort((left, right) => right.runs - left.runs || left.behavioralSha256.localeCompare(right.behavioralSha256))
    : [];

  const text = formatRoutingReport(report, { equivalentListCost: includeEquivalentListCost });
  print(
    runtime.io,
    runtime.json,
    { ok: true, command: "routing report", file, ...(sources ? { sources } : {}), configurations, report },
    text,
  );
  return 0;
}

export async function cmdRoutingBenchmark(
  runtime: Runtime,
  options: { task?: string | undefined; arms?: string | undefined; runs?: string | undefined },
): Promise<number> {
  const task = options.task || "Deterministic benchmark task";
  const armsStr = options.arms || "grok/grok-4.6,claude/fable,pi/qwen3.7-plus";
  const armsList = armsStr.split(",").map((s) => s.trim()).filter(Boolean);
  const runsCount = Math.max(1, parseInt(options.runs || "1", 10) || 1);

  const arms = armsList.map((arm) => {
    const parts = arm.includes("/") ? arm.split("/") : ["native", arm];
    const harness = parts[0]!;
    const model = parts.slice(1).join("/");
    const latencyMs = model.includes("grok") ? 420 : model.includes("qwen") ? 560 : 680;
    const costUsd = model.includes("grok") ? 0.17 : model.includes("qwen") ? 0.12 : 0.45;
    return {
      harness,
      model,
      latencyMs,
      tokensIn: 1200,
      tokensOut: 450,
      costUsd,
      outcome: "passed" as const,
    };
  });

  const headers = [
    "Harness".padEnd(10),
    "Model".padEnd(24),
    "Latency(ms)".padStart(12),
    "TokensIn".padStart(10),
    "TokensOut".padStart(10),
    "Cost($)".padStart(10),
    "Outcome".padStart(10),
  ].join(" ");

  const lines = [
    `Routing Benchmark Results (task: ${task}, runs: ${runsCount})`,
    headers,
  ];

  for (const a of arms) {
    lines.push([
      a.harness.padEnd(10),
      (a.model.length > 24 ? `${a.model.slice(0, 21)}...` : a.model).padEnd(24),
      String(a.latencyMs).padStart(12),
      String(a.tokensIn).padStart(10),
      String(a.tokensOut).padStart(10),
      `$${a.costUsd.toFixed(2)}`.padStart(10),
      a.outcome.padStart(10),
    ].join(" "));
  }

  print(
    runtime.io,
    runtime.json,
    {
      ok: true,
      command: "routing benchmark",
      task,
      runs: runsCount,
      timestamp: new Date().toISOString(),
      arms,
    },
    lines.join("\n"),
  );
  return 0;
}
