/**
 * olam yolo — First-class CLI primitive for tmux-yolo dispatch.
 *
 * Provisions a git worktree + tmux window + injects a yolo
 * (claude --dangerously-skip-permissions) with the given prompt file.
 *
 * Modes:
 *   olam yolo <name> --branch <feat/branch-name> --prompt-file <path>   → Spawn
 *   olam yolo --list                                                     → List active yolo windows
 *   olam yolo --cleanup <name> [--force]                                 → Remove worktree + kill window + delete branch
 *
 * Lessons baked in from /tmux-yolo skill usage:
 *   - Tmux binary varies: /opt/homebrew/Cellar/tmux/3.6a/bin/tmux on this host.
 *   - Session id is "0" by default.
 *   - Worktree root: env OLAM_TMUX_YOLO_ROOT > ~/.olam-tmux-yolo-root file > default sibling dir.
 *   - Yolo boot time is ~8s before prompt can be injected safely.
 *
 * @see docs/runbooks/olam-yolo-cli.md
 */
import * as childProcess from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import pc from 'picocolors';
import { printError, printWarning, printHeader, printInfo, printSuccess } from '../output.js';
// ── Tuneable constants ───────────────────────────────────────────────────────
/** Milliseconds to wait for yolo to boot before injecting the prompt. */
export const YOLO_BOOT_DELAY_MS = 8000;
/** Milliseconds to wait after prompt injection before capturing the pane. */
const CAPTURE_DELAY_MS = 4000;
/** Tmux binary probe order. Skip paths that look like shell plugin shims. */
const TMUX_PROBE_PATHS = [
    '/opt/homebrew/Cellar/tmux/3.6a/bin/tmux',
    '/opt/homebrew/bin/tmux',
    '/usr/local/bin/tmux',
    '/usr/bin/tmux',
];
/** Strings in a tmux binary path that indicate a shell-plugin shim (skip these). */
const TMUX_SHIM_MARKERS = ['.oh-my-zsh', '.zsh', '.bash', 'nvm', 'asdf'];
// ── Default implementations ──────────────────────────────────────────────────
function defaultRunner(cmd, args, opts) {
    const result = childProcess.spawnSync(cmd, args, {
        encoding: 'utf-8',
        cwd: opts?.cwd,
    });
    return {
        status: result.status,
        stdout: typeof result.stdout === 'string' ? result.stdout.trim() : '',
        stderr: typeof result.stderr === 'string' ? result.stderr.trim() : '',
    };
}
function defaultSleeper(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
// ── Tmux binary detection ────────────────────────────────────────────────────
/**
 * Detect a usable tmux binary.
 * Probes known paths first, then falls back to `which tmux`.
 * Skips any path that contains a shell-plugin shim marker.
 */
export function detectTmuxBinary(runner = defaultRunner) {
    for (const p of TMUX_PROBE_PATHS) {
        if (TMUX_SHIM_MARKERS.some((m) => p.includes(m)))
            continue;
        if (fs.existsSync(p))
            return p;
    }
    // Fall back to `which tmux` (may be a shim — check)
    const result = runner('which', ['tmux']);
    if (result.status === 0 && result.stdout) {
        const p = result.stdout.trim();
        if (TMUX_SHIM_MARKERS.some((m) => p.includes(m)))
            return null;
        return p;
    }
    return null;
}
// ── Worktree root resolution ─────────────────────────────────────────────────
/**
 * Resolve the worktree root directory in order:
 *   1. OLAM_TMUX_YOLO_ROOT env var
 *   2. Single-line path in ~/.olam-tmux-yolo-root
 *   3. Default: <dirname(repoRoot)>/<basename(repoRoot)>-wt
 *
 * repoRoot is the git top-level of the current working directory.
 */
export function resolveWorktreeRoot(runner = defaultRunner, cwd = process.cwd()) {
    // 1. Env var
    if (process.env['OLAM_TMUX_YOLO_ROOT']) {
        return process.env['OLAM_TMUX_YOLO_ROOT'];
    }
    // 2. File
    const rootFile = path.join(os.homedir(), '.olam-tmux-yolo-root');
    if (fs.existsSync(rootFile)) {
        const line = fs.readFileSync(rootFile, 'utf-8').trim();
        if (line)
            return line;
    }
    // 3. Default: derive from git top-level
    const gitResult = runner('git', ['rev-parse', '--show-toplevel'], { cwd });
    if (gitResult.status !== 0 || !gitResult.stdout) {
        // Not in a git repo — use cwd as fallback
        return path.join(cwd, '..', path.basename(cwd) + '-wt');
    }
    const repoRoot = gitResult.stdout.trim();
    return path.join(path.dirname(repoRoot), path.basename(repoRoot) + '-wt');
}
// ── Yolo command resolution ──────────────────────────────────────────────────
/**
 * Resolve the yolo command to inject into tmux.
 * Prefers `yolo` alias if it's on PATH and not a shim.
 * Falls back to ~/.local/bin/claude --dangerously-skip-permissions.
 */
export function resolveYoloCommand(runner = defaultRunner) {
    const result = runner('bash', ['-lc', 'command -v yolo']);
    if (result.status === 0 && result.stdout) {
        const p = result.stdout.trim();
        if (p && !TMUX_SHIM_MARKERS.some((m) => p.includes(m))) {
            return { cmd: 'yolo', usingFallback: false };
        }
    }
    return {
        cmd: '~/.local/bin/claude --dangerously-skip-permissions',
        usingFallback: true,
    };
}
export async function spawnYolo(opts) {
    const runner = opts.runner ?? defaultRunner;
    const sleeper = opts.sleeper ?? defaultSleeper;
    const cwd = opts.cwd ?? process.cwd();
    // 1. Detect tmux binary
    const tmux = detectTmuxBinary(runner);
    if (!tmux) {
        throw new Error('tmux binary not found. Checked: ' +
            TMUX_PROBE_PATHS.join(', ') +
            ' and PATH. Ensure tmux is installed and not shadowed by a shell plugin.');
    }
    // 2. Verify --prompt-file exists and is readable
    try {
        fs.accessSync(opts.promptFile, fs.constants.R_OK);
    }
    catch {
        throw new Error(`Prompt file not found or not readable: ${opts.promptFile}\n` +
            'Create the file with your task description and try again.');
    }
    // 3. Resolve worktree root
    const worktreeRoot = resolveWorktreeRoot(runner, cwd);
    const worktreePath = path.join(worktreeRoot, opts.name);
    // 4. Verify branch doesn't exist
    const branchCheck = runner('git', ['show-ref', '--verify', '--quiet', `refs/heads/${opts.branch}`], { cwd });
    if (branchCheck.status === 0) {
        throw new Error(`Branch "${opts.branch}" already exists.\n` +
            `Run: olam yolo --cleanup ${opts.name}  (or use --force to bypass the merged check)`);
    }
    // 5. Verify worktree directory doesn't exist
    // Design decision: NOT idempotent — error to keep state explicit.
    if (fs.existsSync(worktreePath)) {
        throw new Error(`Worktree directory already exists: ${worktreePath}\n` +
            `Run: olam yolo --cleanup ${opts.name}  to remove it first.`);
    }
    // 6. Resolve base branch
    const base = opts.base ?? (() => {
        const r = runner('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
        return r.status === 0 ? r.stdout.trim() : 'main';
    })();
    // 7. Create the worktree
    const worktreeAdd = runner('git', ['worktree', 'add', '-b', opts.branch, worktreePath, base], { cwd });
    if (worktreeAdd.status !== 0) {
        throw new Error(`git worktree add failed:\n${worktreeAdd.stderr || worktreeAdd.stdout}`);
    }
    // 8. Tmux: open new window
    const newWindow = runner(tmux, ['new-window', '-t', '0:', '-n', opts.name, '-c', worktreePath]);
    if (newWindow.status !== 0) {
        throw new Error(`tmux new-window failed:\n${newWindow.stderr}\n` +
            'Is tmux running? Start a session with: tmux new-session -d -s 0');
    }
    // 9. Resolve yolo command
    const { cmd: yoloCmd, usingFallback } = resolveYoloCommand(runner);
    // 10. Wait for yolo to boot
    await sleeper(YOLO_BOOT_DELAY_MS);
    // 11. Inject the prompt via tmux send-keys
    // CRITICAL: use $(cat <file>) shell expansion inside the tmux pane.
    // The string we pass to tmux send-keys is a literal — the SHELL inside
    // the pane interprets the $(cat ...) when Enter fires.
    const sendKeysArg = `${yoloCmd} "$(cat ${opts.promptFile})"`;
    const sendKeys = runner(tmux, ['send-keys', '-t', `0:${opts.name}`, sendKeysArg, 'Enter']);
    if (sendKeys.status !== 0) {
        throw new Error(`tmux send-keys failed:\n${sendKeys.stderr}`);
    }
    // 12. Wait, then smoke-check capture-pane
    await sleeper(CAPTURE_DELAY_MS);
    const capture = runner(tmux, ['capture-pane', '-t', `0:${opts.name}`, '-p']);
    const captureText = capture.stdout ?? '';
    let captureWarning;
    const YOLO_STARTED_MARKERS = ['Cogitating', 'Welcome to Claude Code', 'Claude Code', 'Human:', '> '];
    const started = YOLO_STARTED_MARKERS.some((m) => captureText.includes(m));
    if (!started) {
        captureWarning =
            'yolo may not have started — pane output does not contain expected markers. ' +
                'Attach to verify: tmux select-window -t 0:' + opts.name;
    }
    // 13. Return summary
    return {
        worktreePath,
        branch: opts.branch,
        windowName: opts.name,
        attachHint: `tmux select-window -t 0:${opts.name}`,
        captureWarning,
        yoloFallbackUsed: usingFallback,
    };
}
export function listYoloWindows(opts = {}) {
    const runner = opts.runner ?? defaultRunner;
    const cwd = opts.cwd ?? process.cwd();
    const tmux = detectTmuxBinary(runner);
    if (!tmux)
        return [];
    const listResult = runner(tmux, ['list-windows', '-t', '0', '-F', '#{window_name}']);
    if (listResult.status !== 0)
        return [];
    const windowNames = listResult.stdout
        .split('\n')
        .map((n) => n.trim())
        .filter(Boolean);
    const worktreeRoot = resolveWorktreeRoot(runner, cwd);
    return windowNames.map((name) => {
        const worktreePath = path.join(worktreeRoot, name);
        return {
            name,
            worktreePath: fs.existsSync(worktreePath) ? worktreePath : undefined,
        };
    });
}
export async function cleanupYolo(opts) {
    const runner = opts.runner ?? defaultRunner;
    const cwd = opts.cwd ?? process.cwd();
    const mainBranch = opts.mainBranch ?? 'main';
    const worktreeRoot = resolveWorktreeRoot(runner, cwd);
    const worktreePath = path.join(worktreeRoot, opts.name);
    // Determine branch name from the worktree (git worktree list output)
    const worktreeList = runner('git', ['worktree', 'list', '--porcelain'], { cwd });
    let branch;
    if (worktreeList.status === 0) {
        // Parse: block lines "worktree <path>\nHEAD ...\nbranch refs/heads/<branch>"
        const lines = worktreeList.stdout.split('\n');
        let inTarget = false;
        for (const line of lines) {
            if (line.startsWith('worktree ')) {
                inTarget = line.slice('worktree '.length).trim() === worktreePath;
            }
            if (inTarget && line.startsWith('branch ')) {
                branch = line.replace('branch refs/heads/', '').trim();
                break;
            }
        }
    }
    // Check merged status (skip with --force)
    if (branch && !opts.force) {
        const mergedCheck = runner('git', ['branch', '--merged', mainBranch], { cwd });
        const mergedBranches = mergedCheck.stdout
            .split('\n')
            .map((b) => b.replace(/^\*?\s*/, '').trim());
        if (!mergedBranches.includes(branch)) {
            throw new Error(`Branch "${branch}" is not merged into ${mainBranch}.\n` +
                `Run: olam yolo --cleanup ${opts.name} --force  to remove anyway.`);
        }
    }
    // 1. Kill tmux window
    const tmux = detectTmuxBinary(runner);
    if (tmux) {
        runner(tmux, ['kill-window', '-t', `0:${opts.name}`]);
    }
    // 2. Remove worktree
    const removeArgs = opts.force
        ? ['worktree', 'remove', '--force', worktreePath]
        : ['worktree', 'remove', worktreePath];
    const worktreeRemove = runner('git', removeArgs, { cwd });
    if (worktreeRemove.status !== 0) {
        // Non-fatal if the worktree dir doesn't exist
        if (!worktreeRemove.stderr.includes('is not a worktree')) {
            printWarning(`git worktree remove: ${worktreeRemove.stderr}`);
        }
    }
    // 3. Delete branch
    if (branch) {
        const branchDeleteArgs = opts.force
            ? ['branch', '-D', branch]
            : ['branch', '-d', branch];
        const branchDelete = runner('git', branchDeleteArgs, { cwd });
        if (branchDelete.status !== 0) {
            printWarning(`git branch delete: ${branchDelete.stderr}`);
        }
    }
}
// ── CLI registration ──────────────────────────────────────────────────────────
export function registerYolo(program) {
    const cmd = program
        .command('yolo')
        .description('Dispatch a parallel Claude Code session in a new tmux window + isolated git worktree')
        .argument('[name]', 'Short identifier for the yolo session (kebab-case)')
        .option('--branch <branch>', 'Branch to create in the worktree')
        .option('--base <base>', 'Base branch to fork from (default: current branch)')
        .option('--prompt-file <path>', 'Path to a file containing the task prompt')
        .option('--list', 'List active yolo tmux windows and matched worktree paths')
        .option('--cleanup <name>', 'Remove worktree + kill tmux window + delete branch')
        .option('--force', 'With --cleanup: bypass merged-branch check')
        .action(async (name, opts) => {
        // ── --list ──
        if (opts.list) {
            const windows = listYoloWindows();
            if (windows.length === 0) {
                console.log(pc.dim('No active yolo windows found (tmux session 0).'));
                return;
            }
            printHeader(`${windows.length} yolo window(s)`);
            for (const w of windows) {
                const wt = w.worktreePath ? pc.dim(w.worktreePath) : pc.dim('(no worktree)');
                console.log(`  ${pc.bold(w.name.padEnd(24))} ${wt}`);
            }
            return;
        }
        // ── --cleanup ──
        if (opts.cleanup) {
            try {
                await cleanupYolo({
                    name: opts.cleanup,
                    force: opts.force,
                });
                printSuccess(`cleaned up yolo "${opts.cleanup}"`);
            }
            catch (err) {
                printError(err instanceof Error ? err.message : String(err));
                process.exitCode = 1;
            }
            return;
        }
        // ── Spawn ──
        if (!name) {
            cmd.help();
            return;
        }
        if (!opts.branch) {
            printError('--branch is required for spawn. Example: --branch feat/my-task');
            process.exitCode = 1;
            return;
        }
        if (!opts.promptFile) {
            printError('--prompt-file is required for spawn. Create a file with your task description.');
            process.exitCode = 1;
            return;
        }
        try {
            const result = await spawnYolo({
                name,
                branch: opts.branch,
                base: opts.base,
                promptFile: opts.promptFile,
            });
            printHeader('yolo dispatched');
            printInfo('Worktree', result.worktreePath);
            printInfo('Branch', result.branch);
            printInfo('Window', result.windowName);
            printInfo('Attach', result.attachHint);
            if (result.yoloFallbackUsed) {
                console.log(pc.dim('  [note] yolo alias not found on PATH — used ~/.local/bin/claude --dangerously-skip-permissions'));
            }
            if (result.captureWarning) {
                printWarning(result.captureWarning);
            }
        }
        catch (err) {
            printError(err instanceof Error ? err.message : String(err));
            process.exitCode = 1;
        }
    });
}
//# sourceMappingURL=yolo.js.map