import { join } from 'node:path';
import { query } from '@anthropic-ai/claude-agent-sdk';
import { summarizeResult } from '../agent-runtime-types.js';
import { prepareClaudeOutputStyleFile } from '../../services/output-style.js';
import { normalizeAdditionalDirectories, normalizePermissionMode, normalizeSkillNames, prepareConfigDir, resolveBundledClaudeExecutable, buildEnv, normalizeMcpServers } from './sdk-config.js';
import { startClaudeAdapterIfNeeded, getClaudeCodeModel } from './adapter-pool.js';
import { extractClaudeHookEvents, extractThinkingEvents, extractToolUseEvents, extractToolResultEvent, logToolDebug, formatMessage, isAskUserQuestionAutoResult, countUsageTokens, formatUsageLine, normalizeUsage } from './message-format.js';
export class ClaudeCodeRuntime {
    config;
    abortController = null;
    activeQuery = null;
    adapterRun = null;
    constructor(config = {}) {
        this.config = config;
    }
    async execute(prompt, workingDir, options) {
        this.abortController = new AbortController();
        const output = [];
        const cwd = workingDir || process.cwd();
        const startTime = Date.now();
        const MAX_LOG = 500;
        const d = (msg) => console.log(`[claude-code] ${msg.length > MAX_LOG ? msg.slice(0, MAX_LOG) + '...' : msg}`);
        const permissionMode = normalizePermissionMode(this.config.permissionMode);
        const agentDir = options?.configDir;
        const configDir = agentDir ? join(agentDir, '.claude') : undefined;
        if (configDir)
            prepareConfigDir(configDir, agentDir);
        const skillNames = normalizeSkillNames(options?.skills, configDir);
        const outputStyleFile = configDir ? prepareClaudeOutputStyleFile(configDir, options?.outputStyle) : undefined;
        const claudeExecutable = resolveBundledClaudeExecutable();
        this.adapterRun = await startClaudeAdapterIfNeeded(this.config);
        const baseURL = this.adapterRun?.url ?? this.config.baseURL;
        const apiKey = this.adapterRun ? 'default' : this.config.apiKey;
        const model = getClaudeCodeModel(this.config);
        const additionalDirectories = normalizeAdditionalDirectories(cwd, options?.sandboxDirs);
        const sdkMcpServers = normalizeMcpServers(options?.mcpServers, options?.functionTools);
        const sdkMcpServerNames = Object.keys(sdkMcpServers ?? {});
        const startupTimeoutMs = readPositiveIntegerEnv('AGENT_SPACES_CLAUDE_STARTUP_TIMEOUT_MS') ?? 60_000;
        d(`starting | cwd=${cwd} model=${model ?? 'default'} targetModel=${this.config.model ?? 'default'} provider=${this.config.provider ?? 'default'} baseURL=${baseURL ?? 'default'} permissionMode=${permissionMode} maxTurns=${options?.maxTurns ?? '∞'} tools=claude_code mcpServers=${Object.keys(options?.mcpServers ?? {}).join(',') || '-'} skills=${skillNames.join(',') || '-'} configDir=${configDir ?? 'default'} sandboxDirs=${additionalDirectories.join(',') || '-'} claudeExecutable=${claudeExecutable ?? 'sdk-default'}`);
        d(`prompt: ${prompt.slice(0, 300)}${prompt.length > 300 ? '...' : ''}`);
        d(`sdk mcp servers | ${sdkMcpServerNames.join(',') || '-'}`);
        const stderrLines = [];
        let startupTimeoutError;
        let startupWatchdog;
        let sawFirstSdkMessage = false;
        let sessionId = options?.resumeSessionId;
        const emitHook = (event, matcher = '*', payload) => {
            options?.onEvent?.({ type: 'hook_event', event, matcher, payload });
        };
        try {
            emitHook('SessionStart', '*', {
                cwd,
                model,
                provider: this.config.provider,
                baseURL,
                permissionMode,
                configDir,
                sandboxDirs: additionalDirectories,
                resumeSessionId: options?.resumeSessionId,
            });
            const hookUserPrompt = options?.userPrompt ?? prompt;
            emitHook('UserPromptSubmit', '*', {
                prompt: hookUserPrompt,
                message: hookUserPrompt,
                userMessage: hookUserPrompt,
                fullPrompt: prompt,
                cwd,
                configDir,
            });
            if (/CLAUDE\.md|AGENTS\.md|\.claude\/rules\//.test(prompt)) {
                emitHook('InstructionsLoaded', '*', { source: 'prompt', promptPreview: prompt.slice(0, 1000) });
            }
            const queryOptions = {
                cwd,
                model,
                maxTurns: options?.maxTurns,
                pathToClaudeCodeExecutable: claudeExecutable,
                tools: { type: 'preset', preset: 'claude_code' },
                mcpServers: sdkMcpServers,
                skills: skillNames,
                outputStyle: outputStyleFile,
                managedSettings: {
                    strictPluginOnlyCustomization: ['mcp'],
                },
                settingSources: ['user', 'project', 'local'],
                strictMcpConfig: true,
                additionalDirectories,
                permissionMode,
                allowDangerouslySkipPermissions: permissionMode === 'bypassPermissions' ? true : undefined,
                resume: options?.resumeSessionId,
                abortController: this.abortController,
                env: buildEnv(this.config, configDir, { baseURL, apiKey }),
                stderr: (data) => {
                    const line = data.trim();
                    if (line) {
                        stderrLines.push(line);
                        d(`stderr: ${line}`);
                    }
                },
            };
            this.activeQuery = query({ prompt, options: queryOptions });
            d(`sdk query created | startupTimeoutMs=${startupTimeoutMs}`);
            startupWatchdog = setTimeout(() => {
                if (sawFirstSdkMessage)
                    return;
                startupTimeoutError = `Claude Code startup timed out after ${startupTimeoutMs}ms while waiting for the first SDK message. mcpServers=${sdkMcpServerNames.join(',') || '-'}`;
                d(startupTimeoutError);
                this.abortController?.abort();
                this.activeQuery?.close();
            }, startupTimeoutMs);
            let resultText = '';
            let turns = 0;
            let tokenCount = 0;
            let error;
            let usageLine = null;
            let usage;
            let costUsd;
            let sawResult = false;
            const pendingAskUserQuestionToolIds = new Set();
            let waitingForUserAnswer = false;
            for await (const message of this.activeQuery) {
                if (!sawFirstSdkMessage) {
                    sawFirstSdkMessage = true;
                    if (startupWatchdog) {
                        clearTimeout(startupWatchdog);
                        startupWatchdog = undefined;
                    }
                    d(`first sdk message ${Date.now() - startTime}ms | type=${message.type}`);
                }
                for (const hookEvent of extractClaudeHookEvents(message)) {
                    emitHook(hookEvent.event, hookEvent.matcher, hookEvent.payload);
                }
                const nextSessionId = readSessionId(message);
                if (nextSessionId && nextSessionId !== sessionId) {
                    sessionId = nextSessionId;
                    options?.onEvent?.({ type: 'session', sessionId });
                }
                const toolUses = extractToolUseEvents(message);
                let sawAskUserQuestion = false;
                for (const toolUse of toolUses) {
                    if (toolUse.name === 'AskUserQuestion') {
                        pendingAskUserQuestionToolIds.add(toolUse.id);
                        waitingForUserAnswer = true;
                        sawAskUserQuestion = true;
                    }
                }
                const toolResult = extractToolResultEvent(message);
                const suppressAskUserQuestionResult = Boolean(toolResult
                    && isAskUserQuestionAutoResult(toolResult.result)
                    && (pendingAskUserQuestionToolIds.size > 0
                        || (toolResult.toolUseId ? pendingAskUserQuestionToolIds.has(toolResult.toolUseId) : false)));
                if (suppressAskUserQuestionResult) {
                    waitingForUserAnswer = true;
                }
                logToolDebug(message, d, { suppressAskUserQuestionResult });
                for (const text of extractThinkingEvents(message)) {
                    d(`thinking | ${text}`);
                    options?.onEvent?.({ type: 'reasoning', text, status: 'completed' });
                }
                for (const toolUse of toolUses) {
                    options?.onEvent?.({ type: 'tool_use', ...toolUse });
                }
                if (toolResult && !suppressAskUserQuestionResult) {
                    options?.onEvent?.({ type: 'tool_result', ...toolResult });
                }
                const line = formatMessage(message);
                if (line && !isAskUserQuestionAutoResult(line)) {
                    if (message.type === 'assistant') {
                        d(`assistant | ${line}`);
                    }
                    output.push(line);
                    options?.onEvent?.({ type: 'output', line });
                }
                if (message.type === 'result') {
                    sawResult = true;
                    turns = message.num_turns;
                    tokenCount = countUsageTokens(message.usage);
                    usageLine = formatUsageLine(message.usage);
                    usage = normalizeUsage(message.usage);
                    costUsd = readTotalCostUsd(message);
                    sessionId = readSessionId(message) ?? sessionId;
                    if (message.subtype === 'success') {
                        if (!isAskUserQuestionAutoResult(message.result)) {
                            resultText = message.result;
                        }
                    }
                    else {
                        error = message.errors.join('\n') || message.subtype;
                    }
                }
                if (sawAskUserQuestion)
                    break;
            }
            const elapsed = Date.now() - startTime;
            if (waitingForUserAnswer) {
                d(`waiting for user answer ${elapsed}ms | turns=${turns} tokens=${tokenCount}`);
                const message = resultText || output.at(-1) || 'Waiting for user answer';
                emitHook('Stop', '*', {
                    status: 'waiting_for_user_answer',
                    message,
                    finalMessage: message,
                    output,
                    elapsedMs: elapsed,
                    turns,
                    tokenCount,
                    sessionId,
                });
                if (usageLine)
                    output.push(usageLine);
                return {
                    success: true,
                    summary: 'Waiting for user answer',
                    artifacts: [],
                    output,
                    usage,
                    costUsd,
                    sessionId,
                };
            }
            if (!sawResult) {
                const runtimeError = extractRuntimeError([...stderrLines, ...output])
                    || 'Claude Code execution stopped before reporting a final result';
                d(`failed ${elapsed}ms | turns=${turns} tokens=${tokenCount} | ${runtimeError}`);
                emitHook('StopFailure', '*', { error: runtimeError, elapsedMs: elapsed, turns, tokenCount, sessionId, stderr: stderrLines });
                appendUnique(output, stderrLines);
                appendUnique(output, [runtimeError]);
                return {
                    success: false,
                    summary: 'Claude Code execution failed',
                    artifacts: [],
                    error: runtimeError,
                    output,
                    usage,
                    costUsd,
                    sessionId,
                };
            }
            if (waitingForUserAnswer && (!error || isAskUserQuestionAutoResult(error))) {
                d(`waiting for user answer ${elapsed}ms | turns=${turns} tokens=${tokenCount}`);
                const message = resultText || output.at(-1) || 'Waiting for user answer';
                emitHook('Stop', '*', {
                    status: 'waiting_for_user_answer',
                    message,
                    finalMessage: message,
                    output,
                    elapsedMs: elapsed,
                    turns,
                    tokenCount,
                    sessionId,
                });
                if (usageLine)
                    output.push(usageLine);
                return {
                    success: true,
                    summary: 'Waiting for user answer',
                    artifacts: [],
                    output,
                    usage,
                    costUsd,
                    sessionId,
                };
            }
            if (error) {
                const runtimeError = extractRuntimeError([error, ...stderrLines, ...output]) || error;
                d(`failed ${elapsed}ms | turns=${turns} tokens=${tokenCount} | ${runtimeError}`);
                emitHook('StopFailure', '*', { error: runtimeError, elapsedMs: elapsed, turns, tokenCount, sessionId, stderr: stderrLines });
                appendUnique(output, stderrLines);
                if (usageLine)
                    output.push(usageLine);
                return {
                    success: false,
                    summary: 'Claude Code execution failed',
                    artifacts: [],
                    error: runtimeError,
                    output,
                    usage,
                    costUsd,
                    sessionId,
                };
            }
            const text = resultText || output.at(-1) || '';
            if (text.trim()) {
                d(`final message | ${text.trim()}`);
            }
            d(`done ${elapsed}ms | turns=${turns} tokens=${tokenCount}`);
            const finalOutput = resultText && !output.includes(resultText) ? [...output, resultText] : output;
            if (usageLine && !finalOutput.includes(usageLine))
                finalOutput.push(usageLine);
            emitHook('Stop', '*', {
                status: 'success',
                message: text,
                finalMessage: text,
                output: finalOutput,
                elapsedMs: elapsed,
                turns,
                tokenCount,
                sessionId,
                usage,
                costUsd,
            });
            return {
                success: true,
                summary: summarizeResult(text),
                artifacts: [],
                output: finalOutput,
                usage,
                costUsd,
                sessionId,
            };
        }
        catch (err) {
            const elapsed = Date.now() - startTime;
            const message = startupTimeoutError ?? (err instanceof Error ? err.message : String(err));
            const runtimeError = extractRuntimeError([message, ...stderrLines, ...output]) || message;
            d(`failed ${elapsed}ms | ${runtimeError}`);
            if (err instanceof Error && err.stack)
                console.error(err.stack);
            emitHook('StopFailure', '*', { error: runtimeError, elapsedMs: elapsed, stderr: stderrLines, stack: err instanceof Error ? err.stack : undefined });
            appendUnique(output, stderrLines);
            appendUnique(output, [runtimeError]);
            return { success: false, summary: 'Claude Code execution failed', artifacts: [], error: runtimeError, output, sessionId: options?.resumeSessionId };
        }
        finally {
            if (startupWatchdog)
                clearTimeout(startupWatchdog);
            emitHook('SessionEnd', '*', { cwd, sessionId });
            this.activeQuery?.close();
            this.activeQuery = null;
            await this.adapterRun?.release();
            this.adapterRun = null;
            this.abortController = null;
        }
    }
    stop() {
        this.abortController?.abort();
        void this.activeQuery?.interrupt().catch(() => {
            this.activeQuery?.close();
        });
    }
}
function readPositiveIntegerEnv(name) {
    const value = process.env[name]?.trim();
    if (!value)
        return undefined;
    const parsed = Number(value);
    return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
}
function readSessionId(message) {
    if (!message || typeof message !== 'object')
        return undefined;
    const value = message.session_id;
    return typeof value === 'string' && value.trim() ? value : undefined;
}
function readTotalCostUsd(message) {
    if (!message || typeof message !== 'object')
        return undefined;
    const value = message.total_cost_usd;
    return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
function extractRuntimeError(lines) {
    const text = lines
        .filter((line) => typeof line === 'string' && line.trim().length > 0)
        .join('\n');
    if (!text)
        return undefined;
    const match = text.match(/(?:API Error|Request rejected|Too Many Requests|rate limit|overloaded|429)[^\n]*/i);
    return match?.[0]?.trim();
}
function appendUnique(target, lines) {
    for (const line of lines) {
        if (!line || target.includes(line))
            continue;
        target.push(line);
    }
}
//# sourceMappingURL=index.js.map