/**
 * aider.ts — Aider headless invoker.
 *
 * Spawns:  aider --message "<prompt>" --yes --no-pretty --no-git
 * Parses:  Text output; diff blocks delimited by "--- a/" / "+++ b/" headers.
 *
 * Diff-block detection: consecutive lines that form a unified diff starting
 * with "--- a/" are parsed into file-change events with before/after context.
 * Token usage is estimated from character count (aider doesn't expose tokens
 * directly without --show-token-usage, which adds extra noise).
 */
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { buildEnv, spawnAgent, readLines } from './base.js';
const execFileAsync = promisify(execFile);
/** Rough chars-to-tokens estimate (GPT-4 class: ~4 chars/token). */
function charsToTokens(n) { return Math.round(n / 4); }
export class AiderInvoker {
    id = 'aider';
    label = 'Aider';
    _version = undefined;
    async isAvailable() {
        return (await this.getVersion()) !== null;
    }
    async getVersion() {
        if (this._version !== undefined)
            return this._version;
        try {
            const { stdout } = await execFileAsync('aider', ['--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('aider', ['--message', prompt, '--yes', '--no-pretty', '--no-git'], { cwd: req.workingDir, env, timeoutMs });
        const { process: child, pid, cancel } = spawned;
        let exitCode = 0, totalChars = 0;
        const filesChanged = [];
        let diffBuf = [];
        const t0 = Date.now();
        let _resolveResult;
        const result = new Promise(r => { _resolveResult = r; });
        function* parseDiffBuffer() {
            if (diffBuf.length < 3) {
                diffBuf = [];
                return;
            }
            const raw = diffBuf.join('\n');
            // Extract filename from "--- a/<path>"
            const fromLine = diffBuf.find(l => l.startsWith('--- a/'));
            const toLine = diffBuf.find(l => l.startsWith('+++ b/'));
            if (fromLine && toLine) {
                const filePath = toLine.replace('+++ b/', '');
                if (!filesChanged.includes(filePath))
                    filesChanged.push(filePath);
                yield { kind: 'file-change', path: filePath, before: '', after: raw };
            }
            diffBuf = [];
        }
        async function* eventStream() {
            let step = 0;
            let inDiff = false;
            if (child.stdout) {
                for await (const line of readLines(child.stdout)) {
                    totalChars += line.length;
                    // Diff block detection
                    if (line.startsWith('--- a/') || line.startsWith('+++ b/') ||
                        line.startsWith('@@ ') || (inDiff && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')))) {
                        inDiff = true;
                        diffBuf.push(line);
                        continue;
                    }
                    // End of diff block
                    if (inDiff) {
                        yield* parseDiffBuffer();
                        inDiff = false;
                    }
                    if (line.trim()) {
                        yield { kind: 'stdout', chunk: line };
                        step++;
                        if (step % 5 === 0) {
                            const est = charsToTokens(totalChars);
                            yield { kind: 'token-usage', prompt: est, completion: 0 };
                        }
                        yield { kind: 'progress', step, total: 0, message: line.slice(0, 80) };
                    }
                }
                // Flush any trailing diff
                if (inDiff)
                    yield* parseDiffBuffer();
            }
            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));
            });
            const finalTokens = charsToTokens(totalChars);
            _resolveResult({
                success: exitCode === 0,
                exitCode,
                tokensUsed: { prompt: finalTokens, completion: 0 },
                costUsd: Math.round((finalTokens * 5 / 1_000_000) * 1000) / 1000,
                filesChanged,
                diff: '',
                summary: `aider run completed in ${Date.now() - t0}ms${filesChanged.length ? `, changed ${filesChanged.length} file(s)` : ''}`,
                memoriesFormed: 0,
                durationMs: Date.now() - t0,
            });
            yield { kind: 'token-usage', prompt: finalTokens, completion: 0 };
            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.content}`).join('\n');
    return [
        goal,
        cp.memories.length ? `Prior context:\n${memLines}` : '',
        cp.files.length ? `Focus on: ${cp.files.slice(0, 10).join(', ')}` : '',
    ].filter(Boolean).join('\n\n');
}
