/**
 * Budget Guard Provider
 *
 * Decorator that wraps any LLMProvider and enforces spending limits.
 * Tracks daily/monthly costs, fires notifications, and blocks requests
 * when budget is exceeded.
 *
 * Usage persistence: Stores cost data in a JSON file that survives restarts.
 */
import { readFile, writeFile, mkdir } from 'fs/promises';
import path from 'path';
import { consoleLogger } from '../../utils/logger.js';
/**
 * Error thrown when budget limit is exceeded
 */
export class BudgetExceededError extends Error {
    constructor(budgetType, currentCost, limit) {
        super(`Workers AI ${budgetType} budget exceeded: $${currentCost.toFixed(4)} / $${limit.toFixed(2)} limit`);
        this.name = 'BudgetExceededError';
        this.budgetType = budgetType;
        this.currentCost = currentCost;
        this.limit = limit;
        Object.setPrototypeOf(this, BudgetExceededError.prototype);
    }
}
export class BudgetGuardProvider {
    get name() { return this.inner.name; }
    get defaultModel() { return this.inner.defaultModel; }
    constructor(inner, budget, usageFilePath, notify, logger) {
        this.inner = inner;
        this.budget = budget;
        this.usageFilePath = usageFilePath;
        this.notify = notify;
        this.logger = logger || consoleLogger;
    }
    async analyze(prompt, options) {
        const usage = await this.loadUsage();
        // Check budgets BEFORE making the request
        if (usage.daily.costUSD >= this.budget.dailyLimitUSD) {
            throw new BudgetExceededError('daily', usage.daily.costUSD, this.budget.dailyLimitUSD);
        }
        if (usage.monthly.costUSD >= this.budget.monthlyLimitUSD) {
            throw new BudgetExceededError('monthly', usage.monthly.costUSD, this.budget.monthlyLimitUSD);
        }
        // Make the actual request
        const result = await this.inner.analyze(prompt, options);
        // Update usage counters
        usage.daily.costUSD += result.estimatedCost;
        usage.daily.requests += 1;
        usage.monthly.costUSD += result.estimatedCost;
        usage.monthly.requests += 1;
        usage.total.costUSD += result.estimatedCost;
        usage.total.requests += 1;
        // Save updated usage
        await this.saveUsage(usage);
        // Fire notifications
        this.fireNotifications(usage, result);
        return result;
    }
    async analyzeStructured(prompt, options) {
        // Budget check happens inside analyze() which analyzeStructured delegates to via inner
        const usage = await this.loadUsage();
        if (usage.daily.costUSD >= this.budget.dailyLimitUSD) {
            throw new BudgetExceededError('daily', usage.daily.costUSD, this.budget.dailyLimitUSD);
        }
        if (usage.monthly.costUSD >= this.budget.monthlyLimitUSD) {
            throw new BudgetExceededError('monthly', usage.monthly.costUSD, this.budget.monthlyLimitUSD);
        }
        const result = await this.inner.analyzeStructured(prompt, options);
        usage.daily.costUSD += result.estimatedCost;
        usage.daily.requests += 1;
        usage.monthly.costUSD += result.estimatedCost;
        usage.monthly.requests += 1;
        usage.total.costUSD += result.estimatedCost;
        usage.total.requests += 1;
        await this.saveUsage(usage);
        return result;
    }
    estimateCost(inputTokens, outputTokens, model) {
        return this.inner.estimateCost(inputTokens, outputTokens, model);
    }
    async isAvailable() {
        return this.inner.isAvailable();
    }
    async getStatus() {
        return this.inner.getStatus();
    }
    async loadUsage() {
        const today = new Date().toISOString().slice(0, 10);
        const currentMonth = today.slice(0, 7);
        try {
            const raw = await readFile(this.usageFilePath, 'utf-8');
            const data = JSON.parse(raw);
            // Reset daily counter if date changed
            if (data.daily.date !== today) {
                data.daily = { date: today, costUSD: 0, requests: 0 };
            }
            // Reset monthly counter if month changed
            if (data.monthly.month !== currentMonth) {
                data.monthly = { month: currentMonth, costUSD: 0, requests: 0 };
            }
            return data;
        }
        catch {
            // File doesn't exist or is corrupted — start fresh
            return {
                daily: { date: today, costUSD: 0, requests: 0 },
                monthly: { month: currentMonth, costUSD: 0, requests: 0 },
                total: { costUSD: 0, requests: 0 },
            };
        }
    }
    async saveUsage(usage) {
        try {
            await mkdir(path.dirname(this.usageFilePath), { recursive: true });
            await writeFile(this.usageFilePath, JSON.stringify(usage, null, 2));
        }
        catch (error) {
            this.logger.warn(`Failed to save usage data: ${error.message}`);
        }
    }
    fireNotifications(usage, result) {
        if (!this.notify)
            return;
        // Notify on every use
        if (this.budget.notifyOnUse) {
            this.notify({
                type: 'cost-alert',
                severity: 'info',
                title: 'Workers AI used',
                message: `$${result.estimatedCost.toFixed(4)} spent (${result.model}). Daily: $${usage.daily.costUSD.toFixed(4)}/$${this.budget.dailyLimitUSD.toFixed(2)}`,
                data: {
                    provider: this.inner.name,
                    model: result.model,
                    cost: result.estimatedCost,
                    dailyCost: usage.daily.costUSD,
                    dailyLimit: this.budget.dailyLimitUSD,
                },
            });
        }
        // Check if budget exceeded after this request (still let the request through)
        if (usage.daily.costUSD >= this.budget.dailyLimitUSD) {
            this.notify({
                type: 'cost-alert',
                severity: 'critical',
                title: 'Workers AI daily budget exceeded',
                message: `Daily spend $${usage.daily.costUSD.toFixed(4)} exceeds $${this.budget.dailyLimitUSD.toFixed(2)} limit. Next request will be blocked.`,
                data: { dailyCost: usage.daily.costUSD, dailyLimit: this.budget.dailyLimitUSD },
            });
            return; // Don't also fire threshold warning
        }
        // Check threshold warning
        const dailyPercent = (usage.daily.costUSD / this.budget.dailyLimitUSD) * 100;
        if (dailyPercent >= this.budget.notifyThresholdPercent) {
            this.notify({
                type: 'cost-alert',
                severity: 'warning',
                title: 'Workers AI budget threshold reached',
                message: `Daily spend at ${dailyPercent.toFixed(0)}% ($${usage.daily.costUSD.toFixed(4)}/$${this.budget.dailyLimitUSD.toFixed(2)})`,
                data: { dailyCost: usage.daily.costUSD, dailyLimit: this.budget.dailyLimitUSD, percent: dailyPercent },
            });
        }
    }
}
//# sourceMappingURL=budget-guard.js.map