/**
 * Codex JSON 输出解析器
 * 将 Codex 的 JSONL 输出转换为标准化日志
 */
const DEBUG_PARSER = process.env.DEBUG_PARSER === 'true';
import { createAssistantMessage, createToolUse, createTokenUsageInfo, createErrorMessage, } from './types.js';
import { addNormalizedEntry, replaceNormalizedEntry, updateEntryContent, updateToolStatus, setSessionId, } from './utils/patch.js';
import { stripAnsiSequences } from './utils/ansi.js';
/**
 * Codex 解析器
 */
export class CodexParser {
    msgStore;
    buffer = '';
    currentToolUseId = null;
    currentToolIndex = null;
    toolOutputBuffer = '';
    /** 收集非 JSON 行，用于进程异常退出时作为错误信息 */
    nonJsonLines = [];
    /** 是否已通过 JSON 事件推送过错误（用于 finish 去重） */
    hasJsonError = false;
    /** 已推送的错误消息集合（用于 error / turn.failed 去重） */
    pushedErrors = new Set();
    /** 重试类错误的 entry index（用于原地更新而非创建新条目） */
    retryErrorIndex = null;
    /** mcp_tool_call item.id -> entry index */
    mcpToolEntryMap = new Map();
    constructor(msgStore) {
        this.msgStore = msgStore;
    }
    get indexProvider() {
        return this.msgStore.entryIndex;
    }
    /**
     * 处理数据流
     */
    processData(data) {
        // Strip ANSI escape sequences before buffering. Windows ConPTY injects
        // cursor-control / screen-clear / OSC title sequences into the data
        // stream that would corrupt JSON parsing.
        this.buffer += stripAnsiSequences(data);
        const lines = this.buffer.split('\n');
        this.buffer = lines.pop() || '';
        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed)
                continue;
            // 收集非 JSON 行（如 ERROR 日志），用于异常退出时展示
            if (!trimmed.startsWith('{')) {
                this.nonJsonLines.push(stripAnsiSequences(trimmed));
                if (DEBUG_PARSER) {
                    console.log('[CodexParser] Skipping non-JSON line:', trimmed.substring(0, 100));
                }
                continue;
            }
            this.parseJsonSegments(trimmed);
        }
    }
    /**
     * 解析一行中可能包含的多个 JSON 对象
     * Codex 有时会在同一行输出多个 JSON: {"type":"error",...} {"type":"turn.failed",...}
     */
    parseJsonSegments(line) {
        // 尝试直接解析整行
        try {
            const event = JSON.parse(line);
            this.handleEvent(event);
            return;
        }
        catch {
            // 可能是多个 JSON 拼接，尝试拆分
        }
        const segments = this.extractJsonObjects(line);
        for (const segment of segments) {
            try {
                const event = JSON.parse(segment);
                this.handleEvent(event);
            }
            catch (err) {
                if (DEBUG_PARSER) {
                    console.error('[CodexParser] Failed to parse JSON segment:', segment.substring(0, 200), err);
                }
            }
        }
    }
    /**
     * Extract complete top-level JSON objects from a line without splitting on
     * braces that appear inside JSON strings.
     */
    extractJsonObjects(line) {
        const segments = [];
        let start = -1;
        let depth = 0;
        let inString = false;
        let escaped = false;
        for (let i = 0; i < line.length; i += 1) {
            const char = line[i];
            if (start === -1) {
                if (char === '{') {
                    start = i;
                    depth = 1;
                    inString = false;
                    escaped = false;
                }
                continue;
            }
            if (inString) {
                if (escaped) {
                    escaped = false;
                }
                else if (char === '\\') {
                    escaped = true;
                }
                else if (char === '"') {
                    inString = false;
                }
                continue;
            }
            if (char === '"') {
                inString = true;
            }
            else if (char === '{') {
                depth += 1;
            }
            else if (char === '}') {
                depth -= 1;
                if (depth === 0) {
                    segments.push(line.slice(start, i + 1));
                    start = -1;
                }
            }
        }
        return segments;
    }
    /**
     * 处理事件
     */
    handleEvent(event) {
        if (DEBUG_PARSER) {
            console.log('[CodexParser] Event:', event.type, event);
        }
        switch (event.type) {
            case 'thread.started':
                if (event.thread_id) {
                    this.msgStore.pushSessionId(event.thread_id);
                    const patch = setSessionId(event.thread_id);
                    this.msgStore.pushPatch(patch);
                }
                break;
            case 'item.completed':
                this.handleItemCompleted(event);
                break;
            case 'item.started':
                this.handleItemStarted(event);
                break;
            case 'turn.completed':
                if (event.usage) {
                    this.handleUsage(event.usage);
                }
                break;
            case 'error':
                this.handleError(event.message || 'Unknown error');
                break;
            case 'turn.failed':
                this.handleError(event.error?.message || event.message || 'Turn failed');
                break;
        }
    }
    /**
     * 处理 item.started 事件
     */
    handleItemStarted(event) {
        const item = event.item;
        if (!item)
            return;
        if (item.type === 'mcp_tool_call') {
            this.upsertMcpToolEntry(item, this.getMcpToolStatus(item));
        }
        else if (item.type === 'command_execution' && item.command) {
            // 创建工具使用记录
            const toolUse = createToolUse('bash', item.command, 'command_run', undefined, item.id);
            const index = this.indexProvider.next();
            const patch = addNormalizedEntry(index, toolUse);
            this.msgStore.pushPatch(patch);
            this.currentToolUseId = item.id;
            this.currentToolIndex = index;
            this.toolOutputBuffer = '';
        }
    }
    /**
     * 处理 item.completed 事件
     */
    handleItemCompleted(event) {
        const item = event.item;
        if (!item)
            return;
        if (item.type === 'agent_message' && item.text) {
            // Agent 消息
            const cleanText = stripAnsiSequences(item.text);
            const message = createAssistantMessage(cleanText);
            const index = this.indexProvider.next();
            const patch = addNormalizedEntry(index, message);
            this.msgStore.pushPatch(patch);
        }
        else if (item.type === 'mcp_tool_call') {
            this.upsertMcpToolEntry(item, this.getMcpToolStatus(item));
        }
        else if (item.type === 'command_execution') {
            if (this.currentToolUseId === item.id && this.currentToolIndex !== null) {
                const status = item.exit_code === 0 ? 'success' : 'failed';
                const patch = updateToolStatus(this.currentToolIndex, status);
                this.msgStore.pushPatch(patch);
                if (item.aggregated_output) {
                    this.appendToolResultContent(this.currentToolIndex, stripAnsiSequences(item.aggregated_output));
                }
                this.currentToolUseId = null;
                this.currentToolIndex = null;
                this.toolOutputBuffer = '';
            }
        }
    }
    upsertMcpToolEntry(item, status) {
        const entry = this.createMcpToolEntry(item, status);
        const existingIndex = this.mcpToolEntryMap.get(item.id);
        if (existingIndex !== undefined) {
            const patch = replaceNormalizedEntry(existingIndex, entry);
            this.msgStore.pushPatch(patch);
            return;
        }
        const index = this.indexProvider.next();
        this.mcpToolEntryMap.set(item.id, index);
        const patch = addNormalizedEntry(index, entry);
        this.msgStore.pushPatch(patch);
    }
    createMcpToolEntry(item, status) {
        const toolName = item.tool || 'mcp';
        const content = this.formatMcpToolContent(item);
        const entry = createToolUse(toolName, content, 'tool', item.id, item.id);
        return {
            ...entry,
            metadata: {
                ...entry.metadata,
                status,
            },
        };
    }
    formatMcpToolContent(item) {
        const target = item.server && item.tool ? `${item.server}/${item.tool}` : item.tool || item.server || 'mcp';
        const lines = [`MCP tool call: ${target}`];
        if (item.server)
            lines.push(`Server: ${item.server}`);
        if (item.tool)
            lines.push(`Tool: ${item.tool}`);
        if (item.status)
            lines.push(`Status: ${item.status}`);
        if (item.arguments !== undefined)
            lines.push(`Arguments: ${this.formatJsonValue(item.arguments)}`);
        if (item.result !== undefined && item.result !== null)
            lines.push(`Result: ${this.formatJsonValue(item.result)}`);
        if (item.error !== undefined && item.error !== null)
            lines.push(`Error: ${this.formatJsonValue(item.error)}`);
        return lines.join('\n');
    }
    formatJsonValue(value) {
        if (typeof value === 'string') {
            return stripAnsiSequences(value);
        }
        try {
            return stripAnsiSequences(JSON.stringify(value, null, 2));
        }
        catch {
            return stripAnsiSequences(String(value));
        }
    }
    getMcpToolStatus(item) {
        const rawStatus = item.status?.toLowerCase();
        if (item.error != null)
            return 'failed';
        if (rawStatus === 'completed' || rawStatus === 'success' || rawStatus === 'succeeded')
            return 'success';
        if (rawStatus === 'failed' || rawStatus === 'error' || rawStatus === 'cancelled' || rawStatus === 'canceled')
            return 'failed';
        if (rawStatus === 'denied')
            return 'denied';
        if (rawStatus === 'pending_approval')
            return 'pending_approval';
        if (rawStatus === 'timed_out' || rawStatus === 'timeout')
            return 'timed_out';
        return 'created';
    }
    /**
     * 将工具结果追加到对应 entry 的 content 中
     */
    appendToolResultContent(entryIndex, resultContent) {
        const trimmed = resultContent.trim();
        if (!trimmed)
            return;
        const snapshot = this.msgStore.getSnapshot();
        const entry = snapshot.entries[entryIndex];
        if (!entry)
            return;
        const MAX_RESULT_LENGTH = 20_000;
        const truncated = trimmed.length > MAX_RESULT_LENGTH
            ? trimmed.slice(0, MAX_RESULT_LENGTH) + `\n... (truncated, ${trimmed.length} chars total)`
            : trimmed;
        const newContent = entry.content + '\n\n' + truncated;
        const patch = updateEntryContent(entryIndex, newContent);
        this.msgStore.pushPatch(patch);
    }
    /**
     * 处理 token 使用信息
     */
    handleUsage(usage) {
        if (!usage)
            return;
        // 计算总 token 数：input_tokens + output_tokens
        // 注意：cached_input_tokens 是 input_tokens 的一部分，不应该额外相加
        const totalTokens = (usage.input_tokens || 0) + (usage.output_tokens || 0);
        const tokenUsage = createTokenUsageInfo(totalTokens);
        const index = this.indexProvider.next();
        const patch = addNormalizedEntry(index, tokenUsage);
        this.msgStore.pushPatch(patch);
    }
    /**
     * 检测是否为重试类消息（如 "Reconnecting... 3/5 (reason)"）
     */
    isRetryMessage(message) {
        return /Reconnecting\.\.\.\s*\d+\/\d+/i.test(message);
    }
    /**
     * 处理错误事件（type: error / turn.failed）
     * - 重试类消息（Reconnecting... N/M）：原地更新同一个 entry
     * - 普通错误：去重后创建新 entry
     */
    handleError(message) {
        this.hasJsonError = true;
        if (this.isRetryMessage(message)) {
            if (this.retryErrorIndex !== null) {
                // 原地更新已有的重试 entry
                const patch = updateEntryContent(this.retryErrorIndex, message);
                this.msgStore.pushPatch(patch);
            }
            else {
                // 第一次重试：创建 entry 并记录 index
                const entry = createErrorMessage(message, message);
                const index = this.indexProvider.next();
                const patch = addNormalizedEntry(index, entry);
                this.msgStore.pushPatch(patch);
                this.retryErrorIndex = index;
            }
            return;
        }
        // 非重试消息：检查是否与最后一次重试的根因相同
        // 如 "Reconnecting... 5/5 (reason)" 之后紧跟 "reason" → 更新而非新建
        if (this.retryErrorIndex !== null) {
            const patch = updateEntryContent(this.retryErrorIndex, message);
            this.msgStore.pushPatch(patch);
            this.retryErrorIndex = null;
            this.pushedErrors.add(message);
            return;
        }
        // 普通去重
        if (this.pushedErrors.has(message))
            return;
        this.pushedErrors.add(message);
        const entry = createErrorMessage(message, message);
        const index = this.indexProvider.next();
        const patch = addNormalizedEntry(index, entry);
        this.msgStore.pushPatch(patch);
    }
    /**
     * 完成解析
     */
    finish(exitCode) {
        if (this.buffer.trim()) {
            this.parseJsonSegments(this.buffer);
        }
        // 进程异常退出且没有通过 JSON 事件推送过错误时，才用 nonJsonLines 作为兜底
        const isFailed = typeof exitCode === 'number' && exitCode !== 0;
        if (isFailed && !this.hasJsonError && this.nonJsonLines.length > 0) {
            const errorText = this.nonJsonLines.join('\n').trim();
            if (errorText) {
                const entry = createErrorMessage(`Agent process exited with code ${exitCode}:\n${errorText}`, errorText);
                const index = this.indexProvider.next();
                const patch = addNormalizedEntry(index, entry);
                this.msgStore.pushPatch(patch);
            }
        }
    }
}
/**
 * 创建 Codex 解析器
 */
export function createCodexParser(msgStore) {
    return new CodexParser(msgStore);
}
//# sourceMappingURL=codex-parser.js.map