/**
 * olam resume <pr> — Find and enter the world that opened a PR.
 *
 * Accepts a PR number (e.g. `876`), a PR URL (e.g.
 * `https://github.com/pleri/olam/pull/876`), or a branch name
 * (`night/cycle-04-goal-dispatch`). PR number / URL inputs are
 * normalised via `gh pr view <input> --json number,headRefName`;
 * branch-name inputs skip the `gh` round-trip.
 *
 * After resolving the branch, walks the world registry via
 * `WorldManager.listWorlds()` and matches against `WorldMetadata.branch`.
 * On a unique match, execs `olam enter <id>` so the operator drops into
 * the world's terminal. On zero or multiple matches, prints a clear
 * error and exits 1.
 *
 * Language-aligned with Claude Code 2.1.122's `/resume <pr-url>`.
 */
import { execFileSync } from 'node:child_process';
import { loadContext } from '../context.js';
import { printError } from '../output.js';
/** Detect whether the input looks like a PR identifier (number or GitHub URL). */
export function looksLikePrIdentifier(input) {
    if (/^\d+$/.test(input.trim()))
        return true;
    if (/^https?:\/\/[^\s]*\/pull\/\d+(?:[/?#].*)?$/i.test(input.trim()))
        return true;
    return false;
}
/**
 * Run the resume command. Pure: all I/O happens through {@link ResumeDeps}.
 *
 * @returns Process exit code (0 success, 1 error).
 */
export async function runResume(input, deps) {
    const trimmed = input.trim();
    if (trimmed.length === 0) {
        deps.stderr('resume: argument <pr> is required (PR number, URL, or branch name).');
        return 1;
    }
    let resolved;
    try {
        resolved = await deps.resolveInput(trimmed);
    }
    catch (err) {
        deps.stderr(`resume: could not resolve "${trimmed}" via gh pr view — ` +
            (err instanceof Error ? err.message : String(err)));
        return 1;
    }
    const { branch, prNumber } = resolved;
    const worlds = deps.listWorlds();
    const matches = worlds.filter((w) => w.branch === branch);
    const prLabel = prNumber !== undefined ? `PR #${prNumber}` : `branch ${branch}`;
    if (matches.length === 0) {
        deps.stderr(`No active world found for ${prLabel} (branch ${branch}). ` +
            `Worlds visible via olam list.`);
        return 1;
    }
    if (matches.length > 1) {
        deps.stderr(`Multiple worlds match ${prLabel} (branch ${branch}); operator decides:`);
        for (const w of matches) {
            deps.stderr(`  - ${w.name} (${w.id})`);
        }
        return 1;
    }
    // matches.length is exactly 1 here (guarded by the === 0 and > 1 branches
    // above), so matches[0] is definitely defined. TS strict-mode can't narrow
    // array indexing from a length check, so use a non-null assertion.
    const match = matches[0];
    deps.stdout(`Found world ${match.name} (${match.id}) for ${prLabel}. Entering...`);
    try {
        deps.enterWorld(match.id);
    }
    catch (err) {
        deps.stderr(`resume: olam enter ${match.id} failed — ` +
            (err instanceof Error ? err.message : String(err)));
        return 1;
    }
    return 0;
}
/**
 * Production resolver: shells out to `gh pr view` for PR-shaped inputs.
 * Branch-name inputs short-circuit (no gh round-trip).
 */
export function resolveInputViaGh(input) {
    if (!looksLikePrIdentifier(input)) {
        // Treat as a branch name directly. Refuse empties (defensive — runResume
        // already trims/rejects them, but this keeps the helper standalone-safe).
        if (input.length === 0) {
            return Promise.reject(new Error('empty branch name'));
        }
        return Promise.resolve({ branch: input });
    }
    // Use execFileSync with argv array — input is a defensively-validated
    // PR number or URL (regex above), but argv-array still avoids any shell
    // interpolation hazard if validation is ever loosened.
    let stdout;
    try {
        stdout = execFileSync('gh', ['pr', 'view', input, '--json', 'number,headRefName'], {
            encoding: 'utf-8',
            stdio: ['ignore', 'pipe', 'pipe'],
            timeout: 10_000,
        });
    }
    catch (err) {
        // execFileSync throws on non-zero exit OR if `gh` is missing.
        const msg = err instanceof Error ? err.message : String(err);
        return Promise.reject(new Error(`gh pr view failed: ${msg}`));
    }
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch (err) {
        return Promise.reject(new Error(`gh pr view returned non-JSON output: ${err instanceof Error ? err.message : String(err)}`));
    }
    if (typeof parsed.headRefName !== 'string' || parsed.headRefName.length === 0) {
        return Promise.reject(new Error('gh pr view response missing headRefName'));
    }
    const prNumber = typeof parsed.number === 'number' ? parsed.number : undefined;
    return Promise.resolve({ branch: parsed.headRefName, prNumber });
}
/**
 * Commander wiring. Composes existing primitives:
 * 1. `gh pr view` → branch + PR number (skipped when input is already a branch).
 * 2. `WorldManager.listWorlds()` → match by `branch`.
 * 3. `execFileSync('olam', ['enter', id])` → drop the operator into the world.
 */
export function registerResume(program) {
    program
        .command('resume')
        .description('Resume a world by PR number, URL, or branch — finds the world that opened the PR and enters it.')
        .argument('<pr>', 'PR number (876), PR URL, or branch name')
        .addHelpText('after', `
Examples:
  $ olam resume 876
  $ olam resume https://github.com/pleri/olam/pull/876
  $ olam resume night/feat/foo
`)
        .action(async (pr) => {
        const { ctx, error } = await loadContext();
        if (!ctx) {
            printError(error?.message ?? 'Olam is not configured. Run `olam init` first.');
            process.exitCode = 1;
            return;
        }
        const code = await runResume(pr, {
            resolveInput: resolveInputViaGh,
            listWorlds: () => ctx.worldManager.listWorlds().map((w) => ({
                id: w.id,
                name: w.name,
                branch: w.branch,
            })),
            enterWorld: (worldId) => {
                // Re-invoke the CLI binary. process.argv[1] is the entry script path
                // (e.g. /usr/local/bin/olam or .../packages/cli/dist/index.js). Falling
                // back to argv[0] ('node') is meaningless, so refuse when argv[1] is
                // missing rather than guess.
                const entry = process.argv[1];
                if (!entry) {
                    throw new Error('cannot locate CLI entry script (process.argv[1] missing)');
                }
                execFileSync(process.execPath, [entry, 'enter', worldId, '--exec'], {
                    stdio: 'inherit',
                });
            },
            stdout: (line) => console.log(line),
            stderr: (line) => printError(line),
        });
        if (code !== 0) {
            process.exitCode = code;
        }
    });
}
//# sourceMappingURL=resume.js.map