/**
 * TokenBudgetTracker - In-memory per-session token budget tracker
 * Singleton per project (same pattern as MemoryManager)
 * Provides predictive overflow prevention with threshold-based warnings
 */
import { TOKENS } from './constants.js';
// ============================================================================
// Constants
// ============================================================================
const THRESHOLD_PERCENTS = {
    none: 0,
    soft: 65,
    medium: 75,
    hard: 85,
    critical: 95,
};
const ORDERED_LEVELS = [
    'none', 'soft', 'medium', 'hard', 'critical',
];
export class TokenBudgetTracker {
    static instances = new Map();
    static defaultInstance = null;
    totalBudget;
    consumed;
    bySource;
    crossings;
    lastLevel;
    thresholdCallbacks;
    circuitBreakerFailures;
    static MAX_CIRCUIT_FAILURES = 3;
    constructor(totalBudget) {
        this.totalBudget = totalBudget;
        this.consumed = 0;
        this.bySource = this.createEmptySourceMap();
        this.crossings = [];
        this.lastLevel = 'none';
        this.thresholdCallbacks = new Map();
        this.circuitBreakerFailures = 0;
    }
    static getInstance(projectPath, totalBudget = TOKENS.DEFAULT_BUDGET) {
        if (projectPath) {
            const existing = TokenBudgetTracker.instances.get(projectPath);
            if (existing)
                return existing;
            const inst = new TokenBudgetTracker(totalBudget);
            TokenBudgetTracker.instances.set(projectPath, inst);
            return inst;
        }
        if (!TokenBudgetTracker.defaultInstance) {
            TokenBudgetTracker.defaultInstance = new TokenBudgetTracker(totalBudget);
        }
        return TokenBudgetTracker.defaultInstance;
    }
    trackConsumption(source, tokens) {
        this.consumed += tokens;
        this.bySource[source] += tokens;
        const newLevel = this.getCurrentLevel();
        if (this.levelIndex(newLevel) > this.levelIndex(this.lastLevel)) {
            this.crossings.push({
                level: newLevel,
                crossedAt: this.consumed,
                timestamp: new Date().toISOString(),
            });
            // 자동 콜백 실행 (회로 차단기 적용)
            this.fireCallbacks(newLevel);
        }
        this.lastLevel = newLevel;
        return newLevel;
    }
    /**
     * 특정 threshold 도달 시 자동 실행할 콜백 등록.
     * 예: medium(75%) → 자동 압축, hard(85%) → 경고, critical(95%) → 중단
     */
    onThreshold(level, callback) {
        const cbs = this.thresholdCallbacks.get(level) || [];
        cbs.push(callback);
        this.thresholdCallbacks.set(level, cbs);
    }
    /** 회로 차단기: 연속 실패 시 콜백 실행 중단 */
    isCircuitOpen() {
        return this.circuitBreakerFailures >= TokenBudgetTracker.MAX_CIRCUIT_FAILURES;
    }
    resetCircuitBreaker() {
        this.circuitBreakerFailures = 0;
    }
    fireCallbacks(level) {
        if (this.isCircuitOpen())
            return;
        const callbacks = this.thresholdCallbacks.get(level);
        if (!callbacks?.length)
            return;
        const snapshot = this.getSnapshot();
        for (const cb of callbacks) {
            try {
                const result = cb(level, snapshot);
                if (result instanceof Promise) {
                    result.catch(() => { this.circuitBreakerFailures++; });
                }
                this.circuitBreakerFailures = 0; // 성공 시 리셋
            }
            catch {
                this.circuitBreakerFailures++;
            }
        }
    }
    syncToPercent(percent) {
        this.consumed = Math.round(this.totalBudget * (percent / 100));
        this.lastLevel = this.getCurrentLevel();
    }
    getRemainingBudget() {
        return Math.max(0, this.totalBudget - this.consumed);
    }
    getUsagePercent() {
        if (this.totalBudget === 0)
            return 100;
        return Math.min(100, (this.consumed / this.totalBudget) * 100);
    }
    estimateAfterAction(action, estimatedTokens) {
        const projectedConsumed = this.consumed + estimatedTokens;
        const projectedPercent = Math.min(100, (projectedConsumed / this.totalBudget) * 100);
        const projectedLevel = this.levelForPercent(projectedPercent);
        const wouldCross = this.levelIndex(projectedLevel) > this.levelIndex(this.lastLevel);
        return {
            action,
            estimatedTokens,
            wouldCrossThreshold: wouldCross,
            resultingLevel: projectedLevel,
            resultingPercent: Math.round(projectedPercent * 100) / 100,
        };
    }
    shouldThrottle() {
        return this.getUsagePercent() >= THRESHOLD_PERCENTS.medium;
    }
    getCurrentLevel() {
        return this.levelForPercent(this.getUsagePercent());
    }
    getSnapshot() {
        return {
            totalBudget: this.totalBudget,
            consumed: this.consumed,
            remaining: this.getRemainingBudget(),
            usagePercent: Math.round(this.getUsagePercent() * 100) / 100,
            level: this.getCurrentLevel(),
            bySource: { ...this.bySource },
            crossings: [...this.crossings],
        };
    }
    static estimateTokens(text) {
        return Math.ceil(text.length * TOKENS.PER_CHAR_ESTIMATE);
    }
    reset() {
        this.consumed = 0;
        this.bySource = this.createEmptySourceMap();
        this.crossings = [];
        this.lastLevel = 'none';
        this.circuitBreakerFailures = 0;
    }
    static resetInstance() {
        TokenBudgetTracker.instances.clear();
        TokenBudgetTracker.defaultInstance = null;
    }
    // ========================================================================
    // Private helpers
    // ========================================================================
    createEmptySourceMap() {
        return {
            session_rag: 0,
            memory: 0,
            agent: 0,
            compression: 0,
            tool_call: 0,
            user_prompt: 0,
            system: 0,
        };
    }
    levelForPercent(percent) {
        if (percent >= THRESHOLD_PERCENTS.critical)
            return 'critical';
        if (percent >= THRESHOLD_PERCENTS.hard)
            return 'hard';
        if (percent >= THRESHOLD_PERCENTS.medium)
            return 'medium';
        if (percent >= THRESHOLD_PERCENTS.soft)
            return 'soft';
        return 'none';
    }
    levelIndex(level) {
        return ORDERED_LEVELS.indexOf(level);
    }
}
//# sourceMappingURL=TokenBudgetTracker.js.map