/**
 * codex.ts — OpenAI Codex CLI headless invoker.
 *
 * Spawns:  codex exec "<prompt>"
 * Parses:  Text output with interspersed JSON frames delimited by lines that
 *          start with "{" and are valid JSON objects.
 *
 * Known frame shapes (from codex CLI v0.1+):
 *   { event: 'tool_call', tool, args }
 *   { event: 'token_usage', prompt_tokens, completion_tokens }
 *   { event: 'done', exit_code }
 */
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { buildEnv, spawnAgent, readLines } from './base.js';
const execFileAsync = promisify(execFile);
export class CodexInvoker {
    id = 'codex';
    label = 'Codex CLI';
    _version = undefined;
    async isAvailable() {
        return (await this.getVersion()) !== null;
    }
    async getVersion() {
        if (this._version !== undefined)
            return this._version;
        try {
            const { stdout } = await execFileAsync('codex', ['--version'], { timeout: 5_000 });
            this._version = stdout.trim() || 'unknown';
            return this._version;
        }
        catch {
            this._version = null;
            return null;
        }
    }
    async dispatch(req) {
        const prompt = buildPrompt(req);
        const env = buildEnv(req);
        const timeoutMs = req.timeoutMs ?? 600_000;
        const spawned = spawnAgent('codex', ['exec', prompt], {
            cwd: req.workingDir,
            env,
            timeoutMs,
        });
        const { process: child, pid, cancel } = spawned;
        let promptTokens = 0, completionTokens = 0, exitCode = 0;
        const t0 = Date.now();
        let _resolveResult;
        const result = new Promise(r => { _resolveResult = r; });
        async function* eventStream() {
            let step = 0;
            if (child.stdout) {
                for await (const line of readLines(child.stdout)) {
                    // Try JSON-frame first
                    if (line.startsWith('{')) {
                        let frame;
                        try {
                            frame = JSON.parse(line);
                        }
                        catch { /* fall through to text */ }
                        if (frame) {
                            switch (frame.event) {
                                case 'tool_call':
                                    yield { kind: 'tool-call', toolName: frame.tool ?? 'unknown', args: frame.args };
                                    continue;
                                case 'token_usage':
                                    promptTokens += frame.prompt_tokens ?? 0;
                                    completionTokens += frame.completion_tokens ?? 0;
                                    yield { kind: 'token-usage', prompt: promptTokens, completion: completionTokens };
                                    continue;
                                case 'done':
                                    exitCode = frame.exit_code ?? 0;
                                    continue;
                                default:
                                    break;
                            }
                        }
                    }
                    // Plain text line → stdout event + progress
                    if (line.trim()) {
                        yield { kind: 'stdout', chunk: line };
                        step++;
                        yield { kind: 'progress', step, total: 0, message: line.slice(0, 80) };
                    }
                }
            }
            if (child.stderr) {
                for await (const line of readLines(child.stderr)) {
                    if (line.trim())
                        yield { kind: 'stderr', chunk: line };
                }
            }
            exitCode = await new Promise(resolve => {
                if (child.exitCode !== null) {
                    resolve(child.exitCode);
                    return;
                }
                child.once('exit', code => resolve(code ?? 0));
            });
            _resolveResult({
                success: exitCode === 0,
                exitCode,
                tokensUsed: { prompt: promptTokens, completion: completionTokens },
                costUsd: estimateCost(promptTokens, completionTokens),
                filesChanged: [],
                diff: '',
                summary: `codex run completed in ${Date.now() - t0}ms`,
                memoriesFormed: 0,
                durationMs: Date.now() - t0,
            });
            yield { kind: 'done', exitCode };
        }
        return { runId: req.runId, pid, events: eventStream(), cancel, result };
    }
}
function buildPrompt(req) {
    const { goal, contextPack: cp } = req;
    const memLines = cp.memories.slice(0, 6).map(m => `[${m.tier}] ${m.content}`).join('\n');
    const fileList = cp.files.slice(0, 15).join(', ');
    return [
        goal,
        cp.memories.length ? `Context:\n${memLines}` : '',
        cp.files.length ? `Key files: ${fileList}` : '',
    ].filter(Boolean).join('\n\n');
}
function estimateCost(prompt, completion) {
    return Math.round(((prompt * 4 + completion * 16) / 1_000_000) * 1000) / 1000;
}
