/**
 * @fileoverview Run Summary Tracker for Codeman sessions
 *
 * Tracks significant events during a session's lifetime to provide users
 * with a consolidated "what happened while I was away" view.
 *
 * Features:
 * - Event timeline with severity levels
 * - Aggregated statistics (respawn cycles, tokens, active time, etc.)
 * - State stuck detection (warning when state doesn't change for extended periods)
 * - Token milestone tracking (every 50k tokens)
 *
 * @module run-summary
 */
import { v4 as uuidv4 } from 'uuid';
import { createInitialRunSummaryStats, } from './types.js';
import { CLEANUP_CHECK_INTERVAL_MS } from './config/server-timing.js';
/** Maximum events to keep per session (FIFO trimming) */
const MAX_EVENTS = 1000;
/** Trim to this many events when MAX_EVENTS exceeded */
const TRIM_TO_EVENTS = 800;
/** Token milestone interval (track every N tokens) */
const TOKEN_MILESTONE_INTERVAL = 50000;
/** State stuck warning threshold (ms) */
const STATE_STUCK_WARNING_MS = 10 * 60 * 1000; // 10 minutes
/** State stuck check interval (ms) */
const STATE_STUCK_CHECK_INTERVAL = CLEANUP_CHECK_INTERVAL_MS;
/**
 * Tracks events and statistics for a session's run summary.
 *
 * @example
 * ```typescript
 * const tracker = new RunSummaryTracker('session-123', 'My Session');
 *
 * // Record events
 * tracker.addEvent('session_started', 'info', 'Session started');
 * tracker.addEvent('idle_detected', 'success', 'Claude is idle');
 *
 * // Get the summary
 * const summary = tracker.getSummary();
 * ```
 */
export class RunSummaryTracker {
    sessionId;
    sessionName;
    startedAt;
    lastUpdatedAt;
    events = [];
    stats;
    // State tracking for stuck detection
    currentState = null;
    stateEnteredAt = null;
    stateStuckCheckTimer = null;
    stateStuckWarned = false;
    // Token tracking for milestones
    lastTokenMilestone = 0;
    // Active/idle time tracking
    isCurrentlyActive = false;
    lastStatusChangeAt;
    // Respawn cycle tracking
    inRespawnCycle = false;
    constructor(sessionId, sessionName = '') {
        this.sessionId = sessionId;
        this.sessionName = sessionName;
        this.startedAt = Date.now();
        this.lastUpdatedAt = this.startedAt;
        this.lastStatusChangeAt = this.startedAt;
        this.stats = createInitialRunSummaryStats();
        // Start state stuck detection
        this.startStateStuckDetection();
    }
    /**
     * Add an event to the timeline.
     */
    addEvent(type, severity, title, details, metadata) {
        const event = {
            id: uuidv4(),
            timestamp: Date.now(),
            type,
            severity,
            title,
            details,
            metadata,
        };
        this.events.push(event);
        this.lastUpdatedAt = event.timestamp;
        // Trim if needed
        if (this.events.length > MAX_EVENTS) {
            this.events = this.events.slice(-TRIM_TO_EVENTS);
        }
        return event;
    }
    /**
     * Record a state change (for respawn controller states).
     */
    recordStateChange(newState, details) {
        const oldState = this.currentState;
        // Check if entering or leaving a respawn cycle
        if (newState === 'sending_update' && !this.inRespawnCycle) {
            this.inRespawnCycle = true;
            this.addEvent('respawn_cycle_started', 'info', 'Respawn cycle started', details);
        }
        else if (newState === 'watching' && this.inRespawnCycle && oldState !== 'watching') {
            this.inRespawnCycle = false;
            this.stats.totalRespawnCycles++;
            this.addEvent('respawn_cycle_completed', 'success', 'Respawn cycle completed', details);
        }
        // Record state transition
        if (oldState && oldState !== newState) {
            this.stats.stateTransitions++;
            this.addEvent('respawn_state_change', 'info', `State: ${oldState} → ${newState}`, details, {
                from: oldState,
                to: newState,
            });
        }
        // Update state tracking
        this.currentState = newState;
        this.stateEnteredAt = Date.now();
        this.stateStuckWarned = false;
    }
    /**
     * Record when Claude becomes idle.
     */
    recordIdle() {
        const now = Date.now();
        // Update active time if was previously active
        if (this.isCurrentlyActive) {
            this.stats.totalTimeActiveMs += now - this.lastStatusChangeAt;
        }
        this.isCurrentlyActive = false;
        this.lastStatusChangeAt = now;
        this.stats.lastIdleAt = now;
        this.addEvent('idle_detected', 'success', 'Claude is idle', undefined, {
            activeTimeMs: this.stats.totalTimeActiveMs,
        });
    }
    /**
     * Record when Claude starts working.
     */
    recordWorking() {
        const now = Date.now();
        // Update idle time if was previously idle
        // Note: We count the initial idle period (from session start to first working state)
        // even if lastIdleAt hasn't been set yet
        if (!this.isCurrentlyActive) {
            this.stats.totalTimeIdleMs += now - this.lastStatusChangeAt;
        }
        this.isCurrentlyActive = true;
        this.lastStatusChangeAt = now;
        this.stats.lastWorkingAt = now;
        this.addEvent('working_detected', 'info', 'Claude is working');
    }
    /**
     * Record token usage update.
     */
    recordTokens(inputTokens, outputTokens) {
        const total = inputTokens + outputTokens;
        this.stats.totalTokensUsed = total;
        if (total > this.stats.peakTokens) {
            this.stats.peakTokens = total;
        }
        // Check for milestone
        const currentMilestone = Math.floor(total / TOKEN_MILESTONE_INTERVAL) * TOKEN_MILESTONE_INTERVAL;
        if (currentMilestone > this.lastTokenMilestone && currentMilestone > 0) {
            this.lastTokenMilestone = currentMilestone;
            this.addEvent('token_milestone', 'info', `Token milestone: ${this.formatTokens(currentMilestone)}`, `Input: ${this.formatTokens(inputTokens)}, Output: ${this.formatTokens(outputTokens)}`, {
                total,
                input: inputTokens,
                output: outputTokens,
            });
        }
    }
    /**
     * Record an AI idle check result.
     */
    recordAiCheckResult(verdict, confidence) {
        this.stats.aiCheckCount++;
        const isIdle = verdict === 'IDLE';
        this.addEvent('ai_check_result', isIdle ? 'success' : 'info', `AI check: ${verdict}`, confidence !== undefined ? `Confidence: ${confidence}%` : undefined, { verdict, confidence });
    }
    /**
     * Record an auto-compact event.
     */
    recordAutoCompact(tokens, threshold) {
        this.addEvent('auto_compact', 'warning', 'Auto-compact triggered', `Tokens: ${this.formatTokens(tokens)} / Threshold: ${this.formatTokens(threshold)}`, { tokens, threshold });
    }
    /**
     * Record an auto-clear event.
     */
    recordAutoClear(tokens, threshold) {
        this.addEvent('auto_clear', 'warning', 'Auto-clear triggered', `Tokens: ${this.formatTokens(tokens)} / Threshold: ${this.formatTokens(threshold)}`, { tokens, threshold });
    }
    /**
     * Record a Ralph completion detection.
     */
    recordRalphCompletion(phrase) {
        this.addEvent('ralph_completion', 'success', 'Ralph completion detected', `Phrase: ${phrase}`, {
            phrase,
        });
    }
    /**
     * Record a hook event from Claude Code.
     */
    recordHookEvent(eventType, data) {
        const severity = eventType === 'stop' ? 'warning' : eventType === 'permission_prompt' ? 'info' : 'info';
        this.addEvent('hook_event', severity, `Hook: ${eventType}`, data?.tool_name ? `Tool: ${data.tool_name}` : undefined, { eventType, ...data });
    }
    /**
     * Record an error.
     */
    recordError(title, details, metadata) {
        this.stats.errorCount++;
        this.addEvent('error', 'error', title, details, metadata);
    }
    /**
     * Record a warning.
     */
    recordWarning(title, details, metadata) {
        this.stats.warningCount++;
        this.addEvent('warning', 'warning', title, details, metadata);
    }
    /**
     * Record session started.
     */
    recordSessionStarted(mode, workingDir) {
        this.addEvent('session_started', 'success', 'Session started', `Mode: ${mode}, Dir: ${workingDir}`, {
            mode,
            workingDir,
        });
    }
    /**
     * Record session stopped.
     */
    recordSessionStopped() {
        // Finalize active/idle time
        const now = Date.now();
        if (this.isCurrentlyActive) {
            this.stats.totalTimeActiveMs += now - this.lastStatusChangeAt;
        }
        else {
            this.stats.totalTimeIdleMs += now - this.lastStatusChangeAt;
        }
        this.addEvent('session_stopped', 'info', 'Session stopped', `Duration: ${this.formatDuration(now - this.startedAt)}`);
    }
    /**
     * Get the complete run summary.
     */
    getSummary() {
        // Update times before returning
        const now = Date.now();
        const stats = { ...this.stats };
        // Add current active/idle period
        if (this.isCurrentlyActive) {
            stats.totalTimeActiveMs += now - this.lastStatusChangeAt;
        }
        else {
            stats.totalTimeIdleMs += now - this.lastStatusChangeAt;
        }
        return {
            sessionId: this.sessionId,
            sessionName: this.sessionName,
            startedAt: this.startedAt,
            lastUpdatedAt: this.lastUpdatedAt,
            events: [...this.events],
            stats,
        };
    }
    /**
     * Get recent events (for SSE updates).
     */
    getRecentEvents(count = 10) {
        return this.events.slice(-count);
    }
    /**
     * Update session name.
     */
    setSessionName(name) {
        this.sessionName = name;
    }
    /**
     * Stop tracking and clean up.
     */
    stop() {
        if (this.stateStuckCheckTimer) {
            clearInterval(this.stateStuckCheckTimer);
            this.stateStuckCheckTimer = null;
        }
    }
    // ========== Private Methods ==========
    startStateStuckDetection() {
        this.stateStuckCheckTimer = setInterval(() => {
            if (!this.currentState || !this.stateEnteredAt || this.stateStuckWarned) {
                return;
            }
            const timeInState = Date.now() - this.stateEnteredAt;
            if (timeInState >= STATE_STUCK_WARNING_MS) {
                this.stateStuckWarned = true;
                const minutes = Math.floor(timeInState / 60000);
                this.stats.warningCount++;
                this.addEvent('state_stuck', 'warning', `State stuck: ${this.currentState}`, `In state for ${minutes}+ minutes`, { state: this.currentState, durationMs: timeInState });
            }
        }, STATE_STUCK_CHECK_INTERVAL);
    }
    formatTokens(tokens) {
        if (tokens >= 1000000) {
            return `${(tokens / 1000000).toFixed(1)}M`;
        }
        if (tokens >= 1000) {
            return `${(tokens / 1000).toFixed(1)}k`;
        }
        return String(tokens);
    }
    formatDuration(ms) {
        const seconds = Math.floor(ms / 1000);
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        if (hours > 0) {
            return `${hours}h ${minutes % 60}m`;
        }
        if (minutes > 0) {
            return `${minutes}m ${seconds % 60}s`;
        }
        return `${seconds}s`;
    }
}
//# sourceMappingURL=run-summary.js.map