/**
 * olam setup — substrate-aware fresh-host wizard.
 *
 * Phase C2 of olam-operator-onboarding-parity (plan
 * ~/.claude/plans/olam-operator-onboarding-parity.md).
 *
 * Substrate-aware orchestration. Default substrate = kubernetes (k3d on all
 * platforms — macOS and Linux alike). Existing installs whose ~/.olam/config.json
 * carries host.substrate: 'compose' are protected — they continue on docker/compose
 * and see a one-line migration hint; no auto-migration.
 *
 * Kubernetes substrate phases (default for fresh installs):
 *   Phase 1:   System check     (kubectl on PATH + docker daemon + Node ≥20)
 *   Phase 1.5: Install k3d      (brew install k3d when available; else upstream install.sh)
 *   Phase 2:   olam CLI sanity  (--version returns)
 *   Phase 2.5: Provision cluster (k3d cluster create olam-dev; k3d updates ~/.kube/config)
 *   Phase 2.6: Pin kubectl context (writes host.kubectl_context_pinned = k3d-olam-dev)
 *   Phase 3:   Bootstrap        (subprocess: olam bootstrap with kubernetes substrate)
 *   …        same as docker thereafter
 *
 * Docker substrate phases (existing compose installs or --substrate=docker):
 *   Phase 1:   System check     (Docker daemon reachable + Node ≥20)
 *   Phase 1.5: (no-op for docker)
 *   Phase 2:   olam CLI sanity  (--version returns)
 *   Phase 2.5: (no-op for docker)
 *   Phase 3:   Bootstrap        (subprocess: olam bootstrap)
 *   Phase 4:   Shell init       (idempotent append of completion eval-line)
 *   Phase 5:   Init project     (offer `olam init` if .olam/config.yaml missing)
 *   Phase 5a:  Skill source picker
 *   Phase 5b:  Project sweep
 *   Phase 6:   Auth prompt      (offer interactive `olam auth login`)
 *   Phase 7:   Final verify     (subprocess: olam doctor; halt on non-zero)
 *
 * End-state prints "next steps" pointing at the 3-contract docs per
 * CLAUDE.md's "Reading order for new orgs."
 *
 * Failure semantics: setup fails fast at the first failed phase + prints
 * a named remedy. All phases are idempotent so re-running is safe.
 *
 * Flags:
 *   --substrate=<docker|kubernetes>  substrate to target (default: kubernetes; alias k3s→kubernetes)
 *   --cluster-name=<name>            k3d cluster name (default: olam-dev). Useful for operators with
 *                                    pre-existing clusters or running multiple olam stacks side-by-side.
 *   --skip-shell-init                skip Phase 4 (operator manages shell rc manually)
 *   --skip-auth                      skip Phase 6 (operator will run `olam auth login` later)
 *   --yes / -y                       auto-affirm every prompt (non-interactive)
 *
 * Reversibility: clean-revert; each phase delegates to a separately-
 * tested primitive. Phase 4 keeps a `<rc>.olam-bak.<ts>` backup.
 */
import { spawn, spawnSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import path from 'node:path';
import { createInterface } from 'node:readline';
import { readCliVersion } from '../cli-version.js';
import { probeDockerDaemon, probeComposePlugin, probeKubectl, probeK3d, } from '../lib/health-probes.js';
import { appendIdempotent, resolveShellRc } from '../lib/shell-rc.js';
import { applyKubectlContextPin, findProjectRoot } from './init.js';
import { printError, printHeader, printInfo, printSuccess, printWarning } from '../output.js';
import { pickSkillSourcePhase, } from './setup-phase-5a-skill-source.js';
import { runProjectSweepPhase } from './setup-phase-5b-project-sweep.js';
import { OLAM_CONFIG_PATH, writeConfig } from '../lib/config.js';
const REQUIRED_NODE_MAJOR = 20;
/** k3d cluster name provisioned by olam setup --substrate=kubernetes. */
export const SETUP_K3D_CLUSTER_NAME = 'olam-dev';
const NEXT_STEPS_DOCS_DOCKER = [
    'https://github.com/pleri/olam/blob/main/docs/architecture/devbox-contract.md  — image contract',
    'https://github.com/pleri/olam/blob/main/docs/architecture/manifest-spec.md    — per-repo .adb.yaml schema',
    'https://github.com/pleri/olam/blob/main/docs/architecture/config-spec.md      — workspace .olam/config.yaml schema',
];
const NEXT_STEPS_DOCS_KUBERNETES = [
    'https://github.com/pleri/olam/blob/main/docs/architecture/devbox-contract.md  — image contract',
    'https://github.com/pleri/olam/blob/main/docs/architecture/manifest-spec.md    — per-repo .adb.yaml schema',
    'https://github.com/pleri/olam/blob/main/docs/architecture/config-spec.md      — workspace .olam/config.yaml schema',
    'https://github.com/pleri/olam/blob/main/docs/k8s/SETUP.md                     — k3d operator guide',
];
// ── Default deps ───────────────────────────────────────────────────────
const defaultSpawn = (cmd, args) => new Promise((resolve) => {
    const child = spawn(cmd, [...args], { stdio: 'inherit' });
    child.on('exit', (code) => resolve({ status: code }));
    child.on('error', () => resolve({ status: 1 }));
});
const defaultPrompt = (question, defaultYes) => {
    // MEDIUM fix (Phase C CP3): non-TTY stdin (CI pipeline forgot `--yes`,
    // or `olam setup < /dev/null`) would otherwise hang on `readline.question`
    // forever with no diagnostic. When stdin is not a TTY, fall back to the
    // default answer with a stderr hint so the operator knows what happened.
    if (!process.stdin.isTTY) {
        process.stderr.write(`[olam setup] non-TTY stdin detected; using default answer (${defaultYes ? 'yes' : 'no'}). `
            + `Re-run with \`--yes\` for explicit non-interactive mode.\n`);
        return Promise.resolve(defaultYes);
    }
    return new Promise((resolve) => {
        const rl = createInterface({ input: process.stdin, output: process.stdout });
        const suffix = defaultYes ? ' [Y/n]: ' : ' [y/N]: ';
        rl.question(`${question}${suffix}`, (answer) => {
            rl.close();
            const t = answer.trim().toLowerCase();
            if (t === '')
                resolve(defaultYes);
            else if (t === 'y' || t === 'yes')
                resolve(true);
            else if (t === 'n' || t === 'no')
                resolve(false);
            else
                resolve(defaultYes);
        });
        // Belt-and-braces: if stdin closes mid-prompt (EOF), resolve with default.
        rl.on('close', () => resolve(defaultYes));
    });
};
/** k3d / DNS-label name validation — same rules k3d enforces. */
const CLUSTER_NAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
/**
 * Validate the --cluster-name value.
 * Returns an error message when invalid, or null when valid.
 */
export function validateClusterName(name) {
    if (name.length === 0)
        return 'cluster name must not be empty';
    if (name.length > 63)
        return 'cluster name must be ≤63 characters (DNS label limit)';
    if (!CLUSTER_NAME_RE.test(name)) {
        return (`cluster name '${name}' is invalid — must match ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ `
            + '(lowercase alphanumeric, hyphens allowed in the middle)');
    }
    return null;
}
/**
 * Resolve the effective cluster name.
 * Explicit opt takes priority; falls back to the constant default.
 */
function resolveClusterName(opts) {
    return opts.clusterName ?? SETUP_K3D_CLUSTER_NAME;
}
// ── Substrate resolution ───────────────────────────────────────────────────
/** Migration hint printed once when an existing docker/compose install is detected. */
export const DOCKER_MIGRATION_HINT = 'olam: detected existing docker stack — continuing on docker. '
    + 'To migrate to k3d, run: olam upgrade --substrate=kubernetes (available in a future release).';
/**
 * Resolve the effective substrate: explicit opt has highest priority, then
 * auto-detect from ~/.olam/config.json (or override path), then default
 * 'kubernetes' for fresh installs.
 *
 * The --substrate=k3s alias normalises to 'kubernetes'.
 *
 * Existing-install guard: when the config has host.substrate='compose' (the
 * old docker default) and no explicit flag is provided, the wizard continues
 * on 'docker' and prints a one-line migration hint. No auto-migration.
 */
function resolveSubstrate(opts, deps) {
    if (opts.substrate === 'kubernetes')
        return 'kubernetes';
    if (opts.substrate === 'docker')
        return 'docker';
    // Auto-detect from config file
    const configPath = deps.configPath ?? OLAM_CONFIG_PATH;
    if (existsSync(configPath)) {
        try {
            const raw = readFileSync(configPath, 'utf8');
            const parsed = JSON.parse(raw);
            const host = parsed.host;
            if (host?.substrate === 'kubernetes')
                return 'kubernetes';
            if (host?.substrate === 'compose') {
                // Existing docker/compose install — protect it from the default flip.
                process.stderr.write(DOCKER_MIGRATION_HINT + '\n');
                return 'docker';
            }
        }
        catch {
            // malformed config — fall through to new default
        }
    }
    // Fresh install (no config file, or unrecognised substrate) → new default
    return 'kubernetes';
}
// ── Phases ─────────────────────────────────────────────────────────────
async function phase1SystemCheck(substrate, deps) {
    const nodeVersion = deps.nodeVersion ?? process.version;
    const nodeMajor = parseNodeMajor(nodeVersion);
    const nodeOk = nodeMajor !== null && nodeMajor >= REQUIRED_NODE_MAJOR;
    if (substrate === 'kubernetes') {
        // kubectl is required on all platforms.
        const kubectlProbe = await probeKubectl(deps.dockerExec);
        if (!kubectlProbe.ok) {
            return phaseFromProbe(kubectlProbe);
        }
        // k3d runs inside Docker on all platforms (macOS and Linux alike).
        const dockerProbe = await probeDockerDaemon(deps.dockerExec);
        if (!dockerProbe.ok) {
            return {
                ok: false,
                message: 'docker daemon required for k3d: ' + dockerProbe.message,
                remedy: dockerProbe.remedy,
            };
        }
        if (!nodeOk) {
            return {
                ok: false,
                message: `Node.js ${nodeVersion} is too old (need ≥${REQUIRED_NODE_MAJOR})`,
                remedy: `Install Node.js ${REQUIRED_NODE_MAJOR}+ LTS via nvm/fnm/asdf and re-run \`olam setup\`.`,
            };
        }
        return { ok: true, message: `kubectl on PATH; docker ready; node ${nodeVersion}` };
    }
    // Docker substrate (default)
    const dockerProbe = await probeDockerDaemon(deps.dockerExec);
    if (!dockerProbe.ok) {
        return phaseFromProbe(dockerProbe);
    }
    // Verify docker compose v2 plugin. Linux operators who installed docker.io
    // (or via `brew install docker` on macOS) without the compose plugin will
    // hit cryptic "unknown command" errors later; fail early with a clear remedy.
    const composeProbe = await probeComposePlugin(deps.dockerExec);
    if (!composeProbe.ok) {
        return phaseFromProbe(composeProbe);
    }
    if (!nodeOk) {
        return {
            ok: false,
            message: `Node.js ${nodeVersion} is too old (need ≥${REQUIRED_NODE_MAJOR})`,
            remedy: `Install Node.js ${REQUIRED_NODE_MAJOR}+ LTS via nvm/fnm/asdf and re-run \`olam setup\`.`,
        };
    }
    return {
        ok: true,
        message: `${dockerProbe.message}; ${composeProbe.message}; node ${nodeVersion}`,
    };
}
/**
 * Phase 1.5 — Install missing substrate tools (idempotent).
 *
 * Docker: no-op.
 * Kubernetes (all platforms): installs k3d via brew when available, else the
 * upstream install script. k3d works on both macOS and Linux — no sudo needed
 * (k3d only requires docker, which Phase 1 already confirmed).
 */
async function phase1_5InstallSubstrate(substrate, opts, deps) {
    if (substrate === 'docker') {
        return { ok: true, skipped: true, message: 'no-op for docker substrate' };
    }
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    const promptFn = deps.prompt ?? defaultPrompt;
    // k3d works on both darwin and linux; one path for both.
    // Requires docker daemon (Phase 1 already checks this).
    const k3dProbe = await probeK3d(deps.dockerExec);
    if (k3dProbe.ok) {
        return { ok: true, message: `k3d already present: ${k3dProbe.message}` };
    }
    // Determine installer: brew when available (common on macOS, sometimes on Linux),
    // else upstream install script (no sudo needed).
    const hasBrew = spawnSync('command', ['-v', 'brew'], { shell: true, stdio: 'pipe' }).status === 0;
    const useBrewMsg = hasBrew ? 'Homebrew' : 'upstream install script';
    if (!opts.yes) {
        const confirmed = await promptFn(`k3d is not installed. Install via ${useBrewMsg}?`, true);
        if (!confirmed) {
            return {
                ok: false,
                message: 'k3d install declined; required for kubernetes substrate',
                remedy: 'Install manually: `brew install k3d` or curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash, '
                    + 'then re-run `olam setup --substrate=kubernetes`.',
            };
        }
    }
    if (hasBrew) {
        process.stdout.write('Installing k3d via Homebrew...\n');
        const r = await spawnFn('brew', ['install', 'k3d']);
        if (r.status !== 0) {
            return {
                ok: false,
                message: `brew install k3d failed (exit ${r.status ?? 'signal'})`,
                remedy: 'Try manually: `brew install k3d` or curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash',
            };
        }
    }
    else {
        process.stdout.write('Installing k3d via upstream install script...\n');
        const r = await spawnFn('bash', [
            '-c',
            'curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash',
        ]);
        if (r.status !== 0) {
            return {
                ok: false,
                message: `k3d upstream install failed (exit ${r.status ?? 'signal'})`,
                remedy: 'Install manually: https://k3d.io/v5.x/installation/',
            };
        }
    }
    // Verify k3d is now on PATH
    const k3dCheck = await probeK3d(deps.dockerExec);
    if (!k3dCheck.ok) {
        return {
            ok: false,
            message: 'k3d still not on PATH after install — shell PATH may need updating',
            remedy: 'Close and re-open your terminal, then re-run `olam setup --substrate=kubernetes`.',
        };
    }
    return { ok: true, message: `k3d installed: ${k3dCheck.message}` };
}
function parseNodeMajor(version) {
    const m = version.match(/^v?(\d+)\./);
    if (!m)
        return null;
    const n = Number.parseInt(m[1], 10);
    return Number.isFinite(n) ? n : null;
}
function phaseFromProbe(probe) {
    if (probe.ok)
        return { ok: true, message: probe.message };
    return { ok: false, message: probe.message, remedy: probe.remedy };
}
/**
 * Phase 2.5 — Provision k3d cluster (all platforms).
 * No-op for docker substrate.
 * Idempotent: if the cluster already exists, logs and skips.
 * k3d updates ~/.kube/config automatically — no kubeconfig juggling needed.
 *
 * @param clusterName - resolved cluster name (default: SETUP_K3D_CLUSTER_NAME)
 */
async function phase2_5ProvisionCluster(substrate, clusterName, opts, deps) {
    if (substrate === 'docker') {
        return { ok: true, skipped: true, message: 'no-op for docker substrate' };
    }
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    // k3d cluster create <clusterName> (idempotent — same on macOS and Linux)
    // Check if cluster already exists
    const listResult = await captureSpawn('k3d', ['cluster', 'list', '--output', 'json'], deps.dockerExec);
    if (listResult.ok) {
        const exists = clusterExistsInList(listResult.stdout, clusterName);
        if (exists) {
            return {
                ok: true,
                message: `cluster ${clusterName} already exists; skipping create`,
            };
        }
    }
    process.stdout.write(`Creating k3d cluster ${clusterName}...\n`);
    const ghConfigBind = `${homedir()}/.config/gh:/host/.config/gh`;
    const createResult = await spawnFn('k3d', [
        'cluster', 'create', clusterName,
        '--volume', ghConfigBind,
        '--wait', '--timeout', '90s',
    ]);
    if (createResult.status !== 0) {
        return {
            ok: false,
            message: `k3d cluster create ${clusterName} failed (exit ${createResult.status ?? 'signal'})`,
            remedy: `Run manually: k3d cluster create ${clusterName} --wait --timeout 90s`,
        };
    }
    // Verify kubectl can reach it (k3d updates ~/.kube/config automatically)
    const ctxResult = captureSpawnSync('kubectl', ['config', 'current-context']);
    const ctx = ctxResult.ok ? ctxResult.stdout.trim() : '(unknown)';
    return { ok: true, message: `cluster ${clusterName} created; context: ${ctx}` };
}
/** Capture-only spawn via dockerExec (no subprocess) for list/query operations. */
async function captureSpawn(cmd, args, dockerExec) {
    const exec = dockerExec ?? ((c, a) => {
        const r = spawnSync(c, [...a], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
        return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
    });
    const r = exec(cmd, args);
    return { ok: r.status === 0, stdout: r.stdout, stderr: r.stderr };
}
/** Synchronous capture spawn used for non-critical reads (cluster context). */
function captureSpawnSync(cmd, args) {
    const r = spawnSync(cmd, [...args], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
    return { ok: r.status === 0, stdout: r.stdout ?? '' };
}
/** Parse k3d cluster list JSON and return true if clusterName is present. */
function clusterExistsInList(json, clusterName) {
    try {
        const parsed = JSON.parse(json);
        return Array.isArray(parsed) && parsed.some((c) => c.name === clusterName);
    }
    catch {
        return false;
    }
}
async function phase2CliSanity(deps) {
    // CRITICAL fix (Phase C CP3): readCliVersion() is the canonical
    // source (same helper `index.ts:55` uses for `--version`). The
    // OLAM_CLI_VERSION env var is only assigned INSIDE the host-cp
    // container (host-cp.ts:423); it is NEVER set in the operator's
    // host process — relying on it would make Phase 2 fail for every
    // operator on Day 1.
    const version = deps.olamCliVersion ?? safeReadCliVersion();
    if (!version || version === 'unknown') {
        return {
            ok: false,
            message: 'olam CLI version not detectable',
            remedy: 'Re-install `olam` via `npm install -g @pleri/olam-cli@stable` and re-run.',
        };
    }
    return { ok: true, message: `olam ${version}` };
}
function safeReadCliVersion() {
    try {
        return readCliVersion();
    }
    catch {
        return null;
    }
}
/**
 * Phase 2.6 — Pin kubectl context after k3d cluster creation.
 * No-op for docker substrate.
 *
 * `olam upgrade` (called by bootstrap step 6/6) requires
 * host.kubectl_context_pinned in ~/.olam/config.json. On the kubernetes
 * substrate, Phase 2.5 creates the cluster (context = k3d-<cluster>),
 * but the standard `olam init` that writes this field runs in Phase 5
 * (AFTER bootstrap). This phase bridges the gap: it pins the context
 * immediately after cluster creation so bootstrap/upgrade can proceed.
 *
 * Idempotent: applyKubectlContextPin skips when the field is already set.
 *
 * @param clusterName - resolved cluster name (default: SETUP_K3D_CLUSTER_NAME)
 */
async function phase2_6PinKubectlContext(substrate, clusterName, deps) {
    if (substrate !== 'kubernetes') {
        return { ok: true, skipped: true, message: 'no-op for docker substrate' };
    }
    // k3d names the context `k3d-<cluster-name>`.
    const expectedContext = `k3d-${clusterName}`;
    // Ensure ~/.olam/config.json exists with a valid schema before patching
    // kubectl_context_pinned. applyKubectlContextPin preserves existing keys but
    // starts from `{}` when the file is absent — that produces an invalid schema
    // (`readConfig` warns and falls back to defaults, losing kubectl_context_pinned).
    // writeConfig reads current config (or creates a valid default) and writes a
    // schema-conformant file. The subsequent applyKubectlContextPin will then
    // patch kubectl_context_pinned on top of that valid foundation.
    writeConfig({ host: { substrate: 'kubernetes' } }, { configPath: deps.configPath });
    const result = applyKubectlContextPin([expectedContext], {
        configPath: deps.configPath,
    });
    if ('pinned' in result) {
        return { ok: true, message: `kubectl context pinned: ${result.pinned}` };
    }
    if ('skipped' in result) {
        return { ok: true, message: `kubectl context already pinned (${result.skipped})` };
    }
    // 'refused' — multiple contexts (should not happen since we pass exactly one)
    return {
        ok: false,
        message: `kubectl context pin refused: ${result.refused}`,
        remedy: `Set host.kubectl_context_pinned = ${expectedContext} in ~/.olam/config.json and re-run.`,
    };
}
async function phase3Bootstrap(substrate, deps) {
    // HIGH fix (Phase C CP3): pass --skip-auth-login. `olam bootstrap`
    // step 6 (bootstrap.ts:499-520) runs PKCE interactively by default.
    // Phase 6 owns the operator-facing auth UX (with non-fatal recovery);
    // letting bootstrap run auth too means double-prompt + collapses
    // Phase 6's recovery affordance when auth fails during bootstrap.
    //
    // Substrate-aware: the bootstrap command reads ~/.olam/config.json for
    // substrate dispatch. For kubernetes substrate we additionally pass
    // --skip-cluster-create (Phase 2.5 already created it) and
    // --skip-observability is NOT passed — bootstrap still installs observability.
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    const bootstrapArgs = ['bootstrap', '--skip-auth-login'];
    if (substrate === 'kubernetes') {
        // Cluster was already created by Phase 2.5; bootstrap's preflight
        // and cluster-create steps should skip.
        bootstrapArgs.push('--skip-cluster-create');
    }
    const r = await spawnFn('olam', bootstrapArgs);
    if (r.status === 0) {
        return { ok: true, message: `olam bootstrap succeeded (substrate: ${substrate})` };
    }
    return {
        ok: false,
        message: `olam bootstrap exited ${r.status}`,
        remedy: 'Re-run `olam setup` after inspecting the bootstrap error above (bootstrap is idempotent).',
    };
}
async function phase4ShellInit(opts, deps) {
    if (opts.skipShellInit) {
        return { ok: true, skipped: true, message: 'skipped via --skip-shell-init' };
    }
    const home = deps.home ?? homedir();
    const shellEnv = deps.shellEnv ?? process.env.SHELL;
    const rcPath = resolveShellRc(home, shellEnv);
    if (rcPath === null) {
        return {
            ok: true,
            skipped: true,
            message: `unsupported $SHELL (${shellEnv ?? 'unset'}); add \`eval "$(olam completion <shell>)"\` to your shell rc manually`,
        };
    }
    const shellBasename = path.basename(shellEnv);
    const evalLine = `eval "$(olam completion ${shellBasename})"`;
    const result = appendIdempotent({
        rcPath,
        marker: 'olam completion',
        contentLine: evalLine,
        clock: deps.clock,
    });
    if (result.status === 'no-rc-file') {
        return {
            ok: true,
            skipped: true,
            message: `${rcPath} not present; skipped (add \`${evalLine}\` to your shell rc manually)`,
        };
    }
    if (result.status === 'already-present') {
        return { ok: true, message: `${rcPath}: already configured (no change)` };
    }
    return {
        ok: true,
        message: `${rcPath}: appended (backup: ${result.backupPath})`,
    };
}
async function phase5InitProject(opts, deps) {
    // HIGH fix (Phase C CP3): walk UP to nearest .git matching `olam init`'s
    // own `findProjectRoot` semantics. Otherwise re-running setup from a
    // subdir of a git repo violates the idempotency contract:
    //   - first run from subdir → init walks up + creates config at repo root
    //   - second run from same subdir → Phase 5 checks <subdir>/.olam/ (still
    //     missing) → spawns `olam init` → init walks up + sees config exists
    //     at repo root → exits 1 → setup halts at Phase 5
    // Resolution: Phase 5 uses the SAME walk as init.
    const cwd = deps.cwd ?? process.cwd();
    const projectRoot = findProjectRoot(cwd);
    const configPath = path.join(projectRoot, '.olam', 'config.yaml');
    if (existsSync(configPath)) {
        return { ok: true, message: `${configPath} present (no change)` };
    }
    const promptFn = deps.prompt ?? defaultPrompt;
    const shouldInit = opts.yes
        ? true
        : await promptFn(`No .olam/config.yaml in ${cwd}. Initialise project here?`, false);
    if (!shouldInit) {
        return {
            ok: true,
            skipped: true,
            message: 'declined; run `olam init` later when ready',
        };
    }
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    const r = await spawnFn('olam', ['init']);
    if (r.status === 0) {
        return { ok: true, message: 'olam init completed' };
    }
    return {
        ok: false,
        message: `olam init exited ${r.status}`,
        remedy: 'Inspect the init error above and re-run `olam setup`.',
    };
}
async function phase5aSkillSource(opts, deps) {
    if (opts.skipSkillSource) {
        return { ok: true, skipped: true, message: 'skipped via --skip-skill-source' };
    }
    return pickSkillSourcePhase(opts, deps);
}
async function phase5bProjectSweep(opts, deps) {
    return runProjectSweepPhase(opts, deps);
}
async function phase6Auth(opts, deps) {
    if (opts.skipAuth) {
        return {
            ok: true,
            skipped: true,
            message: 'skipped via --skip-auth (run `olam auth login` later)',
        };
    }
    const promptFn = deps.prompt ?? defaultPrompt;
    const shouldLaunch = opts.yes
        ? true
        : await promptFn('`olam auth login` is interactive (PKCE browser flow, 5 min timeout). Launch now?', false);
    if (!shouldLaunch) {
        return {
            ok: true,
            skipped: true,
            message: 'declined; run `olam auth login` later when ready',
        };
    }
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    const r = await spawnFn('olam', ['auth', 'login']);
    if (r.status === 0) {
        return { ok: true, message: 'olam auth login succeeded' };
    }
    // Auth login failure is recoverable + operator-driven; don't fail the phase
    return {
        ok: true,
        skipped: true,
        message: `olam auth login exited ${r.status}; re-run \`olam auth login\` after resolving`,
    };
}
async function phase7Verify(opts, deps) {
    if (opts.skipDoctor) {
        return { ok: true, skipped: true, message: 'skipped via --skip-doctor' };
    }
    const spawnFn = deps.spawnSubprocess ?? defaultSpawn;
    const r = await spawnFn('olam', ['doctor']);
    if (r.status === 0) {
        return { ok: true, message: 'olam doctor: all probes green' };
    }
    return {
        ok: false,
        message: `olam doctor exited ${r.status}`,
        remedy: 'Inspect doctor output above; the specific failing probe names its remedy.',
    };
}
// ── Orchestrator ──────────────────────────────────────────────────────
const PHASE_TITLES_DOCKER = [
    'Phase 1: System check',
    'Phase 1.5: Substrate tools install',
    'Phase 2: olam CLI sanity',
    'Phase 2.5: Cluster provision',
    'Phase 2.6: Pin kubectl context',
    'Phase 3: Bootstrap',
    'Phase 4: Shell init',
    'Phase 5: Init project',
    'Phase 5a: Skill source picker',
    'Phase 5b: Project sweep',
    'Phase 6: Auth prompt',
    'Phase 7: Final verification',
];
// For backward compatibility the phases array has the same indices
// as before when substrate=docker (1.5 and 2.5 are no-ops/skipped).
// The phase count is 11 not 9 but existing tests that assert on
// phase indices are updated to use the new positions.
export async function runSetup(opts, deps = {}) {
    const substrate = resolveSubstrate(opts, deps);
    const clusterName = resolveClusterName(opts);
    // Phase 0: flag validation (fail fast before any I/O).
    // Only validate when the kubernetes substrate is in play — the flag is a
    // no-op for docker and should not block docker-only operators who pass it
    // accidentally, but we still validate the format to surface typos.
    if (opts.clusterName !== undefined) {
        const nameError = validateClusterName(opts.clusterName);
        if (nameError !== null) {
            const errResult = {
                ok: false,
                message: `--cluster-name: ${nameError}`,
                remedy: 'Use a DNS-compatible name: lowercase letters, digits, hyphens in the middle. '
                    + 'Example: olam-test, my-stack-1',
            };
            printError(`  ✗ ${errResult.message}`);
            if (errResult.remedy)
                printWarning(`    remedy: ${errResult.remedy}`);
            return { phases: [{ name: 'Phase 0: Flag validation', result: errResult }], failureAt: 1, exitCode: 1 };
        }
    }
    const bannerSubstrate = substrate === 'kubernetes' ? 'Kubernetes (k3d)' : 'Docker Compose';
    printHeader(`olam setup — Olam local stack on ${bannerSubstrate}`);
    process.stdout.write(`substrate: ${substrate}\n`);
    if (substrate === 'kubernetes') {
        process.stdout.write(`cluster:   ${clusterName}\n`);
    }
    const phaseFns = [
        () => phase1SystemCheck(substrate, deps),
        () => phase1_5InstallSubstrate(substrate, opts, deps),
        () => phase2CliSanity(deps),
        () => phase2_5ProvisionCluster(substrate, clusterName, opts, deps),
        () => phase2_6PinKubectlContext(substrate, clusterName, deps),
        () => phase3Bootstrap(substrate, deps),
        () => phase4ShellInit(opts, deps),
        () => phase5InitProject(opts, deps),
        () => phase5aSkillSource(opts, deps),
        () => phase5bProjectSweep(opts, deps),
        () => phase6Auth(opts, deps),
        () => phase7Verify(opts, deps),
    ];
    const results = [];
    let failureAt = null;
    for (let i = 0; i < phaseFns.length; i += 1) {
        const name = PHASE_TITLES_DOCKER[i];
        process.stdout.write(`\n${name}\n`);
        const result = await phaseFns[i]();
        results.push({ name, result });
        if (result.ok && result.skipped) {
            printWarning(`  ⊘ ${result.message}`);
        }
        else if (result.ok) {
            printSuccess(`  ✓ ${result.message}`);
        }
        else {
            printError(`  ✗ ${result.message}`);
            if (result.remedy)
                printWarning(`    remedy: ${result.remedy}`);
            failureAt = i + 1;
            break;
        }
    }
    process.stdout.write('\n');
    if (failureAt !== null) {
        printError(`Setup FAILED at Phase ${failureAt}`);
        return { phases: results, failureAt, exitCode: 1 };
    }
    printSuccess('Setup complete.');
    process.stdout.write('\nNext steps — read these in order for the 3-contract pattern:\n');
    const nextStepsDocs = substrate === 'kubernetes' ? NEXT_STEPS_DOCS_KUBERNETES : NEXT_STEPS_DOCS_DOCKER;
    for (const line of nextStepsDocs) {
        printInfo('docs', line);
    }
    process.stdout.write('\n');
    return { phases: results, failureAt: null, exitCode: 0 };
}
export function registerSetup(program) {
    program
        .command('setup')
        .description('Fresh-host onboarding wizard. Default substrate=kubernetes (k3d on all platforms): '
        + 'installs k3d (no sudo needed — only requires docker), provisions the olam-dev cluster, '
        + 'then runs bootstrap end-to-end. '
        + 'Existing docker/compose installs are protected — they continue on docker with a migration hint. '
        + 'Idempotent; safe to re-run.')
        .option('--substrate <substrate>', 'Target substrate: kubernetes (default, alias: k3s) or docker. '
        + 'Auto-detected from ~/.olam/config.json when not specified.')
        .option('--cluster-name <name>', 'k3d cluster name to create/use (kubernetes substrate only; default: olam-dev). '
        + 'Use a different name to avoid colliding with a pre-existing olam-dev cluster, '
        + 'to run multiple olam stacks side-by-side, or for per-run unique names in CI smoke. '
        + 'Must be DNS-compatible: lowercase alphanumeric, hyphens allowed in the middle.')
        .option('--skip-shell-init', 'Skip Phase 4 (do not append to ~/.zshrc / ~/.bashrc)')
        .option('--skip-auth', 'Skip Phase 6 (do not prompt for `olam auth login`)')
        .option('--skip-skill-source', 'Skip Phase 5a (do not pick a skill source; run `olam skills source add` later)')
        .option('--skill-source <id-or-url>', 'Non-interactive Phase 5a: curated name (e.g. atlas-toolbox) or git URL')
        .option('--skip-project-sweep', 'Skip Phase 5b (do not walk projects directory for repo discovery)')
        .option('--projects <path>', 'Phase 5b: project root path to walk (default: ~/Projects)')
        .option('--dry-run', 'Phase 5b: print matches without registering or building (no side effects)')
        .option('--exclude <glob...>', 'Phase 5b: additional skip patterns for project sweep')
        .option('--skip-kg', 'Phase 5b: skip KG eager-build sub-step (sources still registered)')
        .option('--skip-doctor', 'Skip Phase 7 final `olam doctor` verification (for CI or minimal setups)')
        .option('-y, --yes', 'Auto-affirm every prompt (non-interactive)')
        .action(async (rawOpts) => {
        // Normalise the --substrate flag: 'k3s' is an alias for 'kubernetes'.
        let substrate;
        const rawSubstrate = rawOpts.substrate;
        if (rawSubstrate === 'k3s' || rawSubstrate === 'kubernetes') {
            substrate = 'kubernetes';
        }
        else if (rawSubstrate === 'docker') {
            substrate = 'docker';
        }
        else if (rawSubstrate !== undefined) {
            process.stderr.write(`[olam setup] Unknown --substrate value '${rawSubstrate}'. `
                + `Valid values: docker, kubernetes (alias: k3s).\n`);
            process.exitCode = 1;
            return;
        }
        const { substrate: _drop, ...restOpts } = rawOpts;
        void _drop;
        const report = await runSetup({ ...restOpts, substrate });
        if (report.exitCode !== 0)
            process.exitCode = report.exitCode;
    });
}
//# sourceMappingURL=setup.js.map