/**
 * Claude CLI provider — spawns the Claude Code CLI with stream-json output
 * and MCP bridge for tool execution.
 *
 * Adapted from platform's streamClaudeCliChat. Platform-specific dependencies
 * (spawnSandboxed, getBinPath, config) are replaced with injectable options.
 */
import { spawn as nodeSpawn } from 'node:child_process';
import { StreamJsonParser } from '../streaming.js';
import { createMcpBridge } from '../mcp-bridge.js';
export class ClaudeCliProvider {
    binPath;
    cwd;
    env;
    model;
    mcpConfigPath;
    spawnFn;
    timeout;
    disallowedTools;
    constructor(options = {}) {
        this.binPath = options.binPath ?? 'claude';
        this.cwd = options.cwd ?? process.cwd();
        this.env = options.env ?? process.env;
        this.model = options.model;
        this.mcpConfigPath = options.mcpConfigPath;
        this.disallowedTools = options.disallowedTools ?? [];
        this.spawnFn = options.spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, { ...opts, stdio: opts.stdio }));
        this.timeout = options.timeout ?? 600_000;
    }
    async *stream(messages, tools, options) {
        const model = options?.model ?? this.model;
        // Format messages into a single prompt for -p mode
        const prompt = formatPrompt(messages);
        const systemPrompt = options?.systemPrompt;
        // Set up MCP bridge for tool access if tools are provided and no config given
        let bridge = null;
        let mcpConfigPath = this.mcpConfigPath;
        if (tools.length > 0 && !mcpConfigPath) {
            // Create MCP bridge with real executor so CLI tool calls go through our
            // safety guards (path traversal, shrink detection, shell blocklist, etc.)
            const executor = options?.executor ?? (async () => ({ result: 'Tool executor not provided', isError: true }));
            bridge = await createMcpBridge(tools, executor, options?.onToolEvent);
            mcpConfigPath = bridge.configPath;
        }
        const args = [
            '-p',
            '--verbose',
            '--output-format',
            'stream-json',
            '--include-partial-messages',
            '--setting-sources',
            'user,local',
            '--dangerously-skip-permissions',
            '--permission-mode',
            'bypassPermissions',
            ...(systemPrompt ? ['--system-prompt', systemPrompt] : []),
            ...(mcpConfigPath ? ['--mcp-config', mcpConfigPath, '--strict-mcp-config'] : []),
            ...(this.disallowedTools.length > 0 ? ['--disallowed-tools', this.disallowedTools.join(' ')] : []),
            ...(model ? ['--model', model] : []),
        ];
        // Spawn the CLI process
        const spawnResult = this.spawnFn(this.binPath, args, { cwd: this.cwd, stdio: ['pipe', 'pipe', 'pipe'], env: this.env });
        const child = 'child' in spawnResult ? spawnResult.child : spawnResult;
        const spawnCleanup = 'cleanup' in spawnResult ? spawnResult.cleanup : undefined;
        const signal = options?.signal;
        if (signal) {
            signal.addEventListener('abort', () => {
                child.kill('SIGTERM');
                setTimeout(() => {
                    if (!child.killed)
                        child.kill('SIGKILL');
                }, 2000);
            }, { once: true });
        }
        // Timeout — per-request override or provider default
        const timeout = options?.timeout ?? this.timeout;
        const timer = setTimeout(() => {
            child.kill('SIGTERM');
        }, timeout);
        child.stdin.write(prompt);
        child.stdin.end();
        // Collect events from the parser
        const events = [];
        let done = false;
        const parser = new StreamJsonParser((event) => {
            events.push(event);
        });
        let stderrBuf = '';
        child.stderr.on('data', (chunk) => {
            stderrBuf += chunk.toString();
        });
        let stdoutBuffer = '';
        child.stdout.on('data', (chunk) => {
            stdoutBuffer += chunk.toString();
            const lines = stdoutBuffer.split('\n');
            stdoutBuffer = lines.pop() || '';
            for (const line of lines) {
                parser.feed(line);
            }
        });
        child.on('close', (code) => {
            clearTimeout(timer);
            // Process remaining buffer
            if (stdoutBuffer.trim()) {
                parser.feed(stdoutBuffer);
            }
            if (code !== 0 && !parser.hasText && stderrBuf.trim()) {
                events.push({ type: 'text_delta', text: `Claude CLI error: ${stderrBuf.trim().slice(0, 500)}` });
            }
            if (!events.some((e) => e.type === 'message_stop')) {
                events.push({ type: 'message_stop', finishReason: code === 0 ? 'stop' : 'error' });
            }
            done = true;
        });
        child.on('error', () => {
            clearTimeout(timer);
            events.push({ type: 'message_stop', finishReason: 'error' });
            done = true;
        });
        // Yield events as they become available
        try {
            while (!done || events.length > 0) {
                if (events.length > 0) {
                    yield events.shift();
                }
                else {
                    await new Promise((r) => setTimeout(r, 10));
                }
            }
        }
        finally {
            bridge?.cleanup();
            spawnCleanup?.();
        }
    }
}
/**
 * Format agent messages into a single prompt string for the CLI's -p mode.
 */
function formatPrompt(messages) {
    const parts = [];
    for (const msg of messages) {
        if (msg.role === 'user') {
            parts.push(`User: ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
        }
        else if (msg.role === 'assistant') {
            parts.push(`Assistant: ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
        }
        else if (msg.role === 'tool') {
            parts.push(`Tool result (${msg.toolCallId}): ${typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)}\n`);
        }
    }
    return parts.join('\n');
}
export function createClaudeCliProvider(options) {
    return new ClaudeCliProvider(options);
}
//# sourceMappingURL=claude-cli.js.map