import { spawn, execSync } from 'child_process';
import path from 'node:path';
import os from 'node:os';
import fs from 'node:fs';
import { sessionJournal } from './session-journal.js';
/** Summarise a tool_use input into a short human-readable string */
function summariseToolInput(name, input) {
    switch (name) {
        case 'Read':
            return `${input.file_path ?? ''}`;
        case 'Edit':
        case 'Write':
            return `${input.file_path ?? ''}`;
        case 'Bash':
            return `${(input.command ?? '').slice(0, 80)}`;
        case 'Glob':
            return `${input.pattern ?? ''}`;
        case 'Grep':
            return `/${input.pattern ?? ''}/`;
        case 'WebFetch':
            return `${input.url ?? ''}`;
        case 'WebSearch':
            return `"${input.query ?? ''}"`;
        default:
            return '';
    }
}
function resolveClaudeBinary() {
    // Try `which claude` first (respects full shell PATH)
    try {
        const resolved = execSync('which claude', { encoding: 'utf-8', timeout: 3000 }).trim();
        if (resolved)
            return resolved;
    }
    catch { /* fallback below */ }
    // Common installation paths
    const home = os.homedir();
    const candidates = [
        path.join(home, '.local', 'bin', 'claude'),
        path.join(home, '.claude', 'bin', 'claude'),
        '/usr/local/bin/claude',
        '/opt/homebrew/bin/claude',
    ];
    for (const candidate of candidates) {
        try {
            execSync(`test -x "${candidate}"`, { timeout: 1000 });
            return candidate;
        }
        catch { /* next */ }
    }
    // Last resort — hope it's in PATH at runtime
    return 'claude';
}
export class ClaudeBridge {
    type = 'claude-code';
    activeProcesses = new Map();
    timeoutMs;
    claudeBinary;
    constructor(config) {
        this.timeoutMs = config?.timeoutMs ?? 300_000;
        this.claudeBinary = resolveClaudeBinary();
        console.log(`[bridge] Using claude binary: ${this.claudeBinary}`);
    }
    send(opts) {
        const key = opts.squadName ?? opts.cwd;
        if (this.activeProcesses.has(key)) {
            opts.onError({
                code: null,
                message: 'Pipeline is running — chat unavailable until pipeline completes',
            });
            return;
        }
        const args = ['-p', '--output-format', 'stream-json', '--verbose', '--dangerously-skip-permissions'];
        if (opts.resumeSessionId) {
            args.push('--resume', opts.resumeSessionId);
        }
        else if (opts.continueSession) {
            args.push('--continue');
        }
        if (opts.maxTurns && opts.maxTurns > 0) {
            args.push('--max-turns', String(opts.maxTurns));
        }
        // Validate cwd exists before spawning
        if (!fs.existsSync(opts.cwd)) {
            console.error(`[bridge] cwd does not exist: ${opts.cwd}`);
            opts.onError({ code: null, message: `Squad directory not found: ${opts.cwd}` });
            return;
        }
        console.log(`[bridge] binary=${this.claudeBinary} cwd=${opts.cwd} key="${key}"`);
        let proc;
        try {
            const spawnEnv = { ...process.env };
            // Ensure ~/.local/bin is in PATH so claude and its deps are reachable
            const localBin = path.join(os.homedir(), '.local', 'bin');
            if (spawnEnv.PATH && !spawnEnv.PATH.includes(localBin)) {
                spawnEnv.PATH = `${localBin}:${spawnEnv.PATH}`;
            }
            proc = spawn(this.claudeBinary, args, {
                cwd: opts.cwd,
                env: spawnEnv,
                stdio: ['pipe', 'pipe', 'pipe'],
            });
        }
        catch (err) {
            console.error(`[bridge] spawn failed synchronously:`, err);
            opts.onError({ code: null, message: 'Claude Code CLI not available' });
            return;
        }
        this.activeProcesses.set(key, proc);
        // Inject session journal context for fresh sessions (not --continue / --resume)
        let finalPrompt = opts.prompt;
        if (!opts.continueSession && !opts.resumeSessionId) {
            const injection = sessionJournal.buildInjection(key);
            if (injection)
                finalPrompt = injection + opts.prompt;
        }
        // Write prompt via stdin (handles long prompts safely)
        console.log(`[bridge] spawn claude PID=${proc.pid} key="${key}" prompt=${finalPrompt.length} chars`);
        proc.stdin?.write(finalPrompt);
        proc.stdin?.end();
        let stderrBuffer = '';
        let fullText = '';
        let lineBuffer = '';
        let settled = false;
        let stdoutErrorMessage = '';
        let killTimer;
        const cleanup = () => {
            this.activeProcesses.delete(key);
            clearTimeout(timeoutTimer);
            clearTimeout(killTimer);
        };
        const timeoutTimer = setTimeout(() => {
            if (settled)
                return;
            proc.kill('SIGTERM');
            killTimer = setTimeout(() => {
                proc.kill('SIGKILL');
            }, 5000);
        }, this.timeoutMs);
        const processJsonLine = (line) => {
            if (!line.trim())
                return;
            try {
                const evt = JSON.parse(line);
                if (evt.type === 'assistant' && evt.message?.content) {
                    for (const block of evt.message.content) {
                        if (block.type === 'text' && block.text) {
                            const text = block.text;
                            fullText += text;
                            opts.onChunk(text);
                            if (opts.onOutputChunk && opts.agentId && opts.agentName) {
                                opts.onOutputChunk(opts.agentId, opts.agentName, text);
                            }
                        }
                        if (block.type === 'tool_use' && opts.onActivity) {
                            const toolName = block.name;
                            const input = (block.input ?? {});
                            const summary = summariseToolInput(toolName, input);
                            opts.onActivity(toolName, summary);
                        }
                    }
                }
                if (evt.type === 'result' && typeof evt.result === 'string') {
                    // Result text is authoritative — use it if we missed chunks
                    if (!fullText.trim() && evt.result.trim()) {
                        fullText = evt.result;
                        opts.onChunk(evt.result);
                    }
                }
                // Capture error events emitted by Claude Code in stdout (e.g. rate limit exceeded)
                if (evt.type === 'error' && evt.error) {
                    const errMsg = typeof evt.error === 'string' ? evt.error : JSON.stringify(evt.error);
                    stdoutErrorMessage = errMsg;
                    console.log(`[bridge] stdout error event: ${errMsg}`);
                }
                // Capture rate limit events for better error messaging
                if (evt.type === 'rate_limit_event' && evt.rate_limit_info) {
                    const info = evt.rate_limit_info;
                    if (info.status === 'blocked' || info.status === 'rate_limited') {
                        stdoutErrorMessage = `Rate limit reached (${info.rateLimitType ?? 'unknown'}, utilization: ${info.utilization ?? '?'})`;
                        console.log(`[bridge] rate limit blocked: ${stdoutErrorMessage}`);
                    }
                }
            }
            catch {
                // Not valid JSON — ignore (could be partial line)
            }
        };
        proc.stdout?.on('data', (data) => {
            const chunk = data.toString('utf-8');
            lineBuffer += chunk;
            // Process complete lines (newline-delimited JSON)
            let newlineIndex;
            while ((newlineIndex = lineBuffer.indexOf('\n')) !== -1) {
                const line = lineBuffer.slice(0, newlineIndex);
                lineBuffer = lineBuffer.slice(newlineIndex + 1);
                processJsonLine(line);
            }
        });
        proc.stderr?.on('data', (data) => {
            const text = data.toString('utf-8');
            stderrBuffer += text;
            console.log(`[bridge] stderr: ${text.slice(0, 300)}`);
        });
        proc.on('error', (err) => {
            console.error(`[bridge] process error for "${key}":`, err.message);
            if (settled)
                return;
            settled = true;
            cleanup();
            opts.onError({ code: null, message: `Claude Code CLI not available: ${err.message}` });
        });
        proc.on('close', (code) => {
            // Process any remaining buffered data
            if (lineBuffer.trim()) {
                processJsonLine(lineBuffer);
                lineBuffer = '';
            }
            console.log(`[bridge] close code=${code} fullText=${fullText.length} chars stderr=${stderrBuffer.length} chars`);
            if (settled)
                return;
            settled = true;
            cleanup();
            if (code !== 0 && code !== null) {
                const stderrLastLine = stderrBuffer.trim().split('\n').pop() ?? '';
                const errorDetail = stdoutErrorMessage || stderrLastLine || 'Unknown error';
                console.log(`[bridge] ERROR: exit ${code} — ${errorDetail}`);
                opts.onError({ code, message: `Exit code ${code}: ${errorDetail}` });
            }
            else {
                console.log(`[bridge] SUCCESS: ${fullText.slice(0, 200)}`);
                // Record action statements into the session journal for future context injection
                sessionJournal.record(key, fullText);
                opts.onDone(fullText);
            }
        });
    }
    isActive(squadName) {
        return this.activeProcesses.has(squadName);
    }
    kill(squadName) {
        if (squadName === '__all__') {
            for (const [key, proc] of this.activeProcesses) {
                console.log(`[claude-bridge] Killing process for "${key}"`);
                proc.kill('SIGTERM');
            }
            this.activeProcesses.clear();
            return;
        }
        const proc = this.activeProcesses.get(squadName);
        if (proc) {
            proc.kill('SIGTERM');
        }
    }
}
//# sourceMappingURL=claude-bridge.js.map