import { actualCost } from '../pricing/index.js';
/** Convert a resolved BudgetConfig + prior ledger spend into the caps the watcher needs. */
export function capsFromConfig(cfg, prior) {
    return {
        perRun: cfg.per_run,
        perDay: cfg.per_day,
        perProject: cfg.per_project,
        perAgent: cfg.per_agent,
        priorDaySpend: prior?.daySpend ?? 0,
        priorProjectSpend: prior?.projectSpend ?? 0,
        priorAgentDaySpend: prior?.agentDaySpend ?? {},
    };
}
/**
 * Create a live spend watcher. `onBreach` fires at most once, on the first
 * event that pushes any active cap from at-or-under to over. After it fires the
 * watcher keeps accumulating (so `runSpend()` stays accurate for the final
 * ledger record) but never calls `onBreach` again.
 */
export function makeLiveSpendWatcher(args) {
    const { caps, onBreach } = args;
    let run = 0;
    // Cross-vendor accumulators, seeded with pre-run ledger spend.
    let day = caps.priorDaySpend ?? 0;
    let project = caps.priorProjectSpend ?? 0;
    const agentDay = {};
    for (const [k, v] of Object.entries(caps.priorAgentDaySpend ?? {})) {
        if (typeof v === 'number')
            agentDay[k] = v;
    }
    let didBreach = false;
    let disposed = false;
    function checkBreach(agent) {
        if (caps.perRun !== undefined && run > caps.perRun) {
            return { cap: 'per_run', limit: caps.perRun, spend: run, runSpend: run };
        }
        if (caps.perDay !== undefined && day > caps.perDay) {
            return { cap: 'per_day', limit: caps.perDay, spend: day, runSpend: run };
        }
        if (caps.perProject !== undefined && project > caps.perProject) {
            return { cap: 'per_project', limit: caps.perProject, spend: project, runSpend: run };
        }
        if (agent && caps.perAgent && caps.perAgent[agent] !== undefined) {
            const limit = caps.perAgent[agent];
            if ((agentDay[agent] ?? 0) > limit) {
                return { cap: 'per_agent', limit, spend: agentDay[agent], agent, runSpend: run };
            }
        }
        return null;
    }
    return {
        feedUsage(event) {
            if (disposed)
                return;
            const { usd } = actualCost(event.model ?? '', {
                inputTokens: event.inputTokens ?? 0,
                outputTokens: event.outputTokens ?? 0,
                cacheReadTokens: event.cacheReadTokens,
                cacheCreationTokens: event.cacheCreationTokens,
            });
            if (usd <= 0)
                return;
            const agent = event.agent ? String(event.agent) : undefined;
            run += usd;
            day += usd;
            project += usd;
            if (agent)
                agentDay[agent] = (agentDay[agent] ?? 0) + usd;
            if (didBreach)
                return;
            const breach = checkBreach(agent);
            if (breach) {
                didBreach = true;
                onBreach(breach);
            }
        },
        runSpend: () => run,
        breached: () => didBreach,
        dispose() {
            disposed = true;
        },
    };
}
/**
 * Incrementally extract usage events from a stream-json chunk. Buffers a partial
 * trailing line across calls (returned in `rest`), parses each complete line,
 * and yields one UsageEvent per line that carries token counts. Provider shapes
 * handled: Claude/`--json` assistant turns (`message.usage` with
 * `input_tokens`/`output_tokens`/`cache_*_input_tokens`) and the flatter
 * `usage.record` shape (`usage.input_tokens`/`output`). Lines that aren't JSON
 * or carry no usage are skipped — this never throws on agent output.
 */
export function extractUsageEvents(chunk, pending, fallbackModel, fallbackAgent) {
    const combined = pending + chunk;
    const lines = combined.split('\n');
    const rest = lines.pop() ?? '';
    const events = [];
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed || trimmed[0] !== '{')
            continue;
        let obj;
        try {
            obj = JSON.parse(trimmed);
        }
        catch {
            continue;
        }
        const ev = usageFromObject(obj, fallbackModel, fallbackAgent);
        if (ev)
            events.push(ev);
    }
    return { events, rest };
}
function usageFromObject(obj, fallbackModel, fallbackAgent) {
    // Claude emits a final `type:"result"` event carrying a TOP-LEVEL cumulative
    // `usage` that already sums every per-turn `message.usage`. Counting both the
    // per-turn turns AND this cumulative total double-counts a multi-turn run
    // (~2x). The canonical session parser (src/lib/session/parse.ts) reads usage
    // ONLY from `message.usage` and extracts nothing from the result line — mirror
    // that here: skip result lines entirely for usage.
    if (obj?.type === 'result')
        return null;
    // Claude stream-json assistant turn.
    const mu = obj?.message?.usage;
    if (mu && (typeof mu.input_tokens === 'number' || typeof mu.output_tokens === 'number')) {
        return {
            agent: fallbackAgent,
            model: obj.message.model ?? fallbackModel,
            inputTokens: mu.input_tokens ?? 0,
            outputTokens: mu.output_tokens ?? 0,
            cacheReadTokens: mu.cache_read_input_tokens,
            cacheCreationTokens: mu.cache_creation_input_tokens,
        };
    }
    // Flatter usage.record / usage shape (Codex / `usage.record`). The result-line
    // guard above already excludes Claude's cumulative result usage, so this only
    // matches genuine per-event usage records.
    const u = obj?.usage;
    if (u && (typeof u.input_tokens === 'number' || typeof u.output === 'number' || typeof u.output_tokens === 'number')) {
        return {
            agent: fallbackAgent,
            model: obj.model ?? u.model ?? fallbackModel,
            inputTokens: u.input_tokens ?? u.inputOther ?? 0,
            outputTokens: u.output_tokens ?? u.output ?? 0,
            cacheReadTokens: u.cache_read_input_tokens,
            cacheCreationTokens: u.cache_creation_input_tokens,
        };
    }
    return null;
}
