"use strict";
/**
 * Retry-from-checkpoint — relaunch the wizard from the current install dir
 * after an error or cancel outro, letting the new child process pick up the
 * existing checkpoint at `~/.amplitude/wizard/runs/<sha256(installDir)>/checkpoint.json`.
 *
 * The user-facing flow:
 *   1. Wizard hits an error / user cancels mid-run.
 *   2. OutroScreen renders the error/cancel view with a "Press R to retry" hint.
 *   3. User presses R. We spawn a detached-but-foreground child node process
 *      that re-runs the wizard binary with the same args (minus `--api-key`,
 *      and with `--install-dir` pinned to the session's installDir).
 *   4. The child reads the checkpoint and resumes from the post-detection
 *      step, skipping intro / region / org-project / framework selection.
 *   5. We wait for the child to exit and propagate its exit code.
 *
 * Why spawn rather than re-run in-process? The wizard's state is sprawled
 * across module-level singletons (UI registry, agent abort, OAuth client,
 * MCP server, etc.). A clean process restart is simpler and guaranteed
 * correct than trying to re-initialize all that state.
 *
 * Critical: do NOT call `process.exit` while the child is still running —
 * that would kill the inherited stdio. Wait for `child.on('exit', …)`.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.pruneArgs = pruneArgs;
exports.clearStaleAgentState = clearStaleAgentState;
exports.buildRetryArgs = buildRetryArgs;
exports.retryFromCheckpoint = retryFromCheckpoint;
const node_child_process_1 = require("node:child_process");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const analytics_1 = require("../utils/analytics");
const storage_paths_1 = require("../utils/storage-paths");
/**
 * Args that should never survive the retry. `--api-key` short-circuits the
 * normal credential resolution (and therefore checkpoint resume), so it must
 * be pruned. `--install-dir` is replaced with the session's installDir
 * explicitly so the retry resumes the right project even if cwd changed.
 */
const PRUNED_ARG_PREFIXES = ['--api-key', '--install-dir'];
/**
 * Strip prefixed args (and their following value, if separated by space)
 * from an argv slice. Handles both `--foo=bar` and `--foo bar` forms.
 */
function pruneArgs(args, prefixes = PRUNED_ARG_PREFIXES) {
    const out = [];
    for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        const matchedPrefix = prefixes.find((p) => arg === p || arg.startsWith(`${p}=`));
        if (matchedPrefix) {
            // If form is `--foo bar` (no `=`), also skip the next token (value).
            if (arg === matchedPrefix && i + 1 < args.length) {
                i += 1;
            }
            continue;
        }
        out.push(arg);
    }
    return out;
}
/**
 * Best-effort cleanup of stale agent-state snapshots owned by THIS process.
 * The new child gets a new pid + attemptId, so it can't collide; we delete
 * our own stragglers so they don't accumulate under `~/.amplitude/wizard/state/`.
 *
 * Files are named `<attemptId>-<pid>.json` (see `getStateFile` in
 * `storage-paths.ts`). We only touch files matching the current pid.
 */
function clearStaleAgentState(pid = process.pid) {
    const stateDir = (0, node_path_1.join)((0, storage_paths_1.getCacheRoot)(), 'state');
    if (!(0, node_fs_1.existsSync)(stateDir))
        return;
    let entries;
    try {
        entries = (0, node_fs_1.readdirSync)(stateDir);
    }
    catch {
        return;
    }
    const pidSuffix = `-${pid}.json`;
    for (const entry of entries) {
        if (!entry.endsWith(pidSuffix))
            continue;
        const full = (0, node_path_1.join)(stateDir, entry);
        try {
            // Sanity: only delete plain files, not directories.
            if ((0, node_fs_1.statSync)(full).isFile())
                (0, node_fs_1.unlinkSync)(full);
        }
        catch {
            // best-effort
        }
    }
}
/**
 * Build the argv list for the retry child process.
 * Exposed for unit testing.
 */
function buildRetryArgs(binPath, rawArgs, installDir) {
    const pruned = pruneArgs(rawArgs);
    return {
        node: process.execPath,
        args: [binPath, ...pruned, '--install-dir', installDir],
    };
}
/**
 * Re-launch the wizard with the same install dir so it picks up the
 * checkpoint. Resolves with the child's exit code; the caller (OutroScreen)
 * does not normally need to inspect it because we propagate it via
 * `process.exit` here.
 *
 * This function is intentionally NOT async-throwing: any failure to spawn
 * is caught and surfaced as exit code 1, matching the "best-effort, not a
 * guarantee" contract of the retry hotkey.
 */
async function retryFromCheckpoint(store, opts = {}) {
    const installDir = store.session.installDir;
    const binPath = opts.binPath ?? process.argv[1];
    const rawArgs = opts.rawArgs ?? process.argv.slice(2);
    const spawnFn = opts.spawnFn ?? node_child_process_1.spawn;
    analytics_1.analytics.wizardCapture('error outro retry pressed', {
        'outro kind': store.session.outroData?.kind ?? null,
        integration: store.session.integration ?? null,
    });
    // If `process.argv[1]` is missing somehow (corrupted invocation, weird
    // wrapper), bail out cleanly with exit code 1. The user can re-run by
    // hand. This is a "best-effort" hotkey, not a contract.
    if (!binPath) {
        if (!opts.skipExit)
            process.exit(1);
        return 1;
    }
    // Drop our own stale agent-state snapshot before the child starts. The
    // new child runs under a different pid + attemptId so it cannot hydrate
    // from our snapshot, but leaving it on disk is just litter.
    clearStaleAgentState();
    const { node, args } = buildRetryArgs(binPath, rawArgs, installDir);
    return new Promise((resolve) => {
        let child;
        try {
            child = spawnFn(node, args, {
                stdio: 'inherit',
                // detached: false so the child shares our process group and Ctrl+C
                // is delivered to it. We're explicitly NOT detaching — we want the
                // child to BE the foreground wizard, taking over our terminal.
                detached: false,
            });
        }
        catch {
            if (!opts.skipExit)
                process.exit(1);
            resolve(1);
            return;
        }
        child.on('error', () => {
            if (!opts.skipExit)
                process.exit(1);
            resolve(1);
        });
        child.on('exit', (code) => {
            const exitCode = code ?? 1;
            if (!opts.skipExit)
                process.exit(exitCode);
            resolve(exitCode);
        });
    });
}
