import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';
/**
 * Parses Claude Code JSONL session logs from ~/.claude/projects/<slug>/
 * to extract session summaries, errors, and usage data.
 *
 * Tool name resolution: when a tool_result has is_error: true, we look up
 * the matching tool_use block (by tool_use_id) from the preceding assistant
 * message to extract the tool name and input args.
 */
export class ClaudeLogParser {
    constructor(projectRoot) {
        /** Cache of parsed sessions keyed by file mtime to avoid re-parsing */
        this.sessionCache = new Map();
        /** In-flight deduplication: concurrent calls share the same pending promise */
        this.pendingSummaries = null;
        /** Aggregate-level cache to avoid re-parsing on rapid successive calls */
        this.summariesCache = null;
        this.summariesCacheTime = 0;
        this.summariesCacheTTL = 30000; // 30 seconds
        const slug = projectRoot.replace(/^\//, '').replace(/\//g, '-');
        this.logDir = path.join(process.env.HOME || '', '.claude/projects', `-${slug}`);
        const fileCount = this.getSessionFiles().length;
        console.log(`[LogParser] logDir=${this.logDir} files=${fileCount}`);
    }
    /** Get recent session summaries with error counts */
    async getSessionSummaries(limit = 50) {
        // Return cached aggregate if fresh
        if (this.summariesCache && Date.now() - this.summariesCacheTime < this.summariesCacheTTL) {
            return this.summariesCache.slice(0, limit);
        }
        // Deduplicate concurrent calls — share the same pending promise
        if (this.pendingSummaries) {
            const result = await this.pendingSummaries;
            return result.slice(0, limit);
        }
        this.pendingSummaries = this.fetchSummaries(limit);
        try {
            const result = await this.pendingSummaries;
            this.summariesCache = result;
            this.summariesCacheTime = Date.now();
            return result;
        }
        finally {
            this.pendingSummaries = null;
        }
    }
    /** Internal: parallel-fetch session summaries */
    async fetchSummaries(limit) {
        const files = this.getSessionFiles();
        const recent = files.slice(-limit);
        const summaries = [];
        const CONCURRENCY = 15;
        for (let i = 0; i < recent.length; i += CONCURRENCY) {
            const batch = recent.slice(i, i + CONCURRENCY);
            const settled = await Promise.allSettled(batch.map(file => this.parseSessionFileCached(file)));
            for (const result of settled) {
                if (result.status === 'fulfilled' && result.value) {
                    summaries.push(result.value);
                }
            }
        }
        summaries.sort((a, b) => b.startTime.localeCompare(a.startTime));
        return summaries;
    }
    /** Get paginated errors with real total count */
    async getRecentErrors(limit = 50, offset = 0, typeFilter, search) {
        // Parse enough sessions to get a comprehensive error view
        const summaries = await this.getSessionSummaries(200);
        let allErrors = [];
        for (const s of summaries) {
            allErrors.push(...s.errors);
        }
        allErrors.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
        // Apply type filter
        if (typeFilter) {
            allErrors = allErrors.filter(e => e.type === typeFilter);
        }
        // Apply search filter (case-insensitive match on message, tool name, tool input)
        if (search) {
            const needle = search.toLowerCase();
            allErrors = allErrors.filter(e => e.message.toLowerCase().includes(needle) ||
                e.context?.toolName?.toLowerCase().includes(needle) ||
                e.context?.toolInput?.toLowerCase().includes(needle) ||
                e.context?.precedingAction?.toLowerCase().includes(needle));
        }
        const total = allErrors.length;
        const errors = allErrors.slice(offset, offset + limit);
        return { total, errors, offset, limit };
    }
    /** Get error statistics grouped by type */
    async getErrorGroups() {
        const { errors: allErrors } = await this.getRecentErrors(10000, 0);
        const groups = new Map();
        for (const err of allErrors) {
            const existing = groups.get(err.type);
            if (existing) {
                existing.count++;
                if (err.timestamp > existing.lastSeen)
                    existing.lastSeen = err.timestamp;
                existing.sessions.add(err.sessionId);
                if (existing.messages.length < 5)
                    existing.messages.push(err.message);
            }
            else {
                groups.set(err.type, {
                    count: 1,
                    lastSeen: err.timestamp,
                    sessions: new Set([err.sessionId]),
                    messages: [err.message],
                });
            }
        }
        return Array.from(groups.entries())
            .map(([type, data]) => ({
            type,
            count: data.count,
            lastSeen: data.lastSeen,
            sessions: data.sessions.size,
            recentMessages: data.messages,
        }))
            .sort((a, b) => b.count - a.count);
    }
    /** Get full session detail with all errors */
    async getSessionDetail(sessionId) {
        const filePath = path.join(this.logDir, `${sessionId}.jsonl`);
        if (!fs.existsSync(filePath))
            return null;
        return this.parseSessionFileCached(filePath);
    }
    /** Get errors grouped into time buckets for timeline visualization */
    async getErrorTimeline(bucketMinutes = 60) {
        const { errors: allErrors } = await this.getRecentErrors(10000, 0);
        if (allErrors.length === 0)
            return [];
        const bucketMs = bucketMinutes * 60 * 1000;
        const buckets = new Map();
        for (const err of allErrors) {
            const ts = new Date(err.timestamp).getTime();
            if (isNaN(ts))
                continue;
            const bucketStart = Math.floor(ts / bucketMs) * bucketMs;
            const existing = buckets.get(bucketStart);
            if (existing) {
                existing.count++;
                existing.byType[err.type] = (existing.byType[err.type] || 0) + 1;
            }
            else {
                buckets.set(bucketStart, { count: 1, byType: { [err.type]: 1 } });
            }
        }
        return Array.from(buckets.entries())
            .sort((a, b) => a[0] - b[0])
            .map(([start, data]) => ({
            start: new Date(start).toISOString(),
            end: new Date(start + bucketMs).toISOString(),
            count: data.count,
            byType: data.byType,
        }));
    }
    /** Parse with mtime-based caching */
    async parseSessionFileCached(filePath) {
        try {
            const stat = fs.statSync(filePath);
            const cached = this.sessionCache.get(filePath);
            if (cached && cached.mtime === stat.mtimeMs) {
                return cached.summary;
            }
            const summary = await this.parseSessionFile(filePath);
            if (summary) {
                this.sessionCache.set(filePath, { mtime: stat.mtimeMs, summary });
            }
            return summary;
        }
        catch {
            return this.parseSessionFile(filePath);
        }
    }
    /** Parse a single JSONL session file */
    async parseSessionFile(filePath) {
        const sessionId = path.basename(filePath, '.jsonl');
        const stream = fs.createReadStream(filePath, { encoding: 'utf-8' });
        const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
        let startTime = '';
        let endTime = '';
        let messageCount = 0;
        let toolCallCount = 0;
        let version = '';
        let gitBranch = '';
        const errors = [];
        // Track tool_use blocks for name resolution: tool_use_id -> { name, input }
        const toolUseMap = new Map();
        // Track recent tool calls for causal context (ring buffer of last 5)
        const recentToolCalls = [];
        // Track what the assistant was last doing (text output or tool call)
        let lastAssistantAction = '';
        for await (const line of rl) {
            if (!line.trim())
                continue;
            try {
                const entry = JSON.parse(line);
                const ts = entry.timestamp || '';
                if (!startTime && ts)
                    startTime = ts;
                if (ts)
                    endTime = ts;
                if (entry.version && !version)
                    version = entry.version;
                if (entry.gitBranch && !gitBranch)
                    gitBranch = entry.gitBranch;
                // Count messages
                if (entry.type === 'user')
                    messageCount++;
                // Track tool_use blocks from assistant messages for name resolution
                if (entry.type === 'assistant') {
                    const content = entry.message?.content;
                    if (Array.isArray(content)) {
                        for (const block of content) {
                            if (block.type === 'tool_use' && block.id && block.name) {
                                toolCallCount++;
                                const inputSummary = this.summarizeToolInput(block.name, block.input);
                                toolUseMap.set(block.id, { name: block.name, input: inputSummary });
                                // Track for causal context
                                recentToolCalls.push({ name: block.name, input: inputSummary });
                                if (recentToolCalls.length > 5)
                                    recentToolCalls.shift();
                                lastAssistantAction = `Tool: ${block.name}(${inputSummary.slice(0, 80)})`;
                            }
                            else if (block.type === 'text' && block.text) {
                                const text = block.text.slice(0, 120).replace(/\n/g, ' ');
                                if (text.trim())
                                    lastAssistantAction = text;
                            }
                        }
                    }
                }
                // Build context snapshot for errors
                const buildContext = (extra) => ({
                    messageIndex: messageCount,
                    precedingAction: lastAssistantAction || undefined,
                    recentTools: recentToolCalls.length > 0 ? [...recentToolCalls.slice(-3)] : undefined,
                    ...extra,
                });
                // Detect system-level errors (API errors, overloaded, etc.)
                if (entry.type === 'system' && entry.level === 'error') {
                    const errorType = this.classifySystemError(entry);
                    const errorMsg = this.extractErrorMessage(entry);
                    errors.push({
                        timestamp: ts,
                        sessionId,
                        type: errorType,
                        message: errorMsg,
                        context: buildContext(),
                    });
                }
                // Detect tool failures (is_error: true in tool results)
                if (entry.type === 'user' && entry.message?.content) {
                    const content = entry.message.content;
                    if (Array.isArray(content)) {
                        for (const block of content) {
                            if (block.is_error && block.type === 'tool_result') {
                                const msg = typeof block.content === 'string'
                                    ? block.content
                                    : JSON.stringify(block.content);
                                // Resolve tool name from the matching tool_use block
                                const toolInfo = block.tool_use_id
                                    ? toolUseMap.get(block.tool_use_id)
                                    : undefined;
                                errors.push({
                                    timestamp: ts,
                                    sessionId,
                                    type: this.classifyToolError(msg),
                                    message: msg,
                                    context: buildContext({
                                        toolName: toolInfo?.name,
                                        toolInput: toolInfo?.input,
                                    }),
                                });
                            }
                        }
                    }
                }
            }
            catch { /* skip malformed lines */ }
        }
        if (!startTime)
            return null;
        return {
            sessionId,
            startTime,
            endTime,
            messageCount,
            toolCallCount,
            errors,
            version,
            gitBranch,
        };
    }
    /** Get sorted list of session JSONL files */
    getSessionFiles() {
        if (!fs.existsSync(this.logDir))
            return [];
        try {
            return fs.readdirSync(this.logDir)
                .filter(f => f.endsWith('.jsonl'))
                .map(f => path.join(this.logDir, f))
                .sort((a, b) => {
                try {
                    return fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs;
                }
                catch {
                    return 0;
                }
            });
        }
        catch {
            return [];
        }
    }
    /** Classify a system error entry */
    classifySystemError(entry) {
        const subtype = entry.subtype || '';
        if (subtype === 'api_error') {
            const errorType = entry.error?.error?.error?.type || '';
            if (errorType === 'overloaded_error')
                return 'rate_limit';
            return 'api_error';
        }
        if (subtype.includes('hook'))
            return 'hook_error';
        return 'unknown';
    }
    /** Classify a tool error message */
    classifyToolError(message) {
        if (message.includes('exceeds maximum allowed tokens') || message.includes('prompt is too long')) {
            return 'prompt_too_long';
        }
        return 'tool_failure';
    }
    /** Extract human-readable error message */
    extractErrorMessage(entry) {
        if (entry.error?.error?.error?.message)
            return entry.error.error.error.message;
        if (entry.error?.message)
            return entry.error.message;
        if (entry.message)
            return typeof entry.message === 'string' ? entry.message : '';
        return entry.subtype || 'Unknown error';
    }
    /** Summarize tool input for context display */
    summarizeToolInput(toolName, input) {
        if (!input)
            return '';
        switch (toolName) {
            case 'Read':
                return input.file_path || '';
            case 'Write':
                return input.file_path || '';
            case 'Edit':
                return input.file_path || '';
            case 'Bash':
                return (input.command || '').slice(0, 200);
            case 'Grep':
                return `${input.pattern || ''} ${input.path || ''}`.trim();
            case 'Glob':
                return `${input.pattern || ''} ${input.path || ''}`.trim();
            case 'Task':
                return input.description || '';
            default:
                // Generic: try common field names
                return input.file_path || input.path || input.command || input.query || '';
        }
    }
}
//# sourceMappingURL=claude-log-parser.js.map