import path from "node:path";
import { nowIso, readJsonFile, withFileLock, writeJsonFile } from "../../shared/index.js";
const BUDGET_SCHEMA_VERSION = 1;
export function runBudgetPath(store, runId) {
    return path.join(path.resolve(store.directory, store.config.trace.artifact_root), runId, "budget.json");
}
export async function initializeRunBudget(store, runId, timestamp = nowIso()) {
    const existing = await readRunBudget(store, runId);
    if (existing)
        return existing;
    const state = {
        schema_version: BUDGET_SCHEMA_VERSION,
        run_id: runId,
        created_at: timestamp,
        updated_at: timestamp,
        limits: {
            max_runtime_hours: positiveNumber(store.config.budget.max_runtime_hours, 100),
            max_child_sessions: positiveInteger(store.config.budget.max_child_sessions, 64),
            max_model_calls: positiveInteger(store.config.budget.max_model_calls, 1000),
            warn_ratio: clampRatio(store.config.budget.warn_ratio, 0.8),
            stale_worker_minutes: positiveNumber(store.config.budget.stale_worker_minutes, 30),
        },
        usage: {
            child_sessions: 0,
            model_calls: 0,
            tokens_by_model: {},
            context: emptyContextUsage(),
        },
    };
    await writeJsonFile(runBudgetPath(store, runId), state);
    return state;
}
export async function readRunBudget(store, runId) {
    const raw = await readJsonFile(runBudgetPath(store, runId));
    return raw ? normalizeRunBudget(store, runId, raw) : undefined;
}
export async function updateRunBudget(store, runId, update) {
    const file = runBudgetPath(store, runId);
    return withFileLock(file, async () => {
        const current = await initializeRunBudget(store, runId);
        const next = update(current);
        const normalized = normalizeRunBudget(store, runId, { ...next, updated_at: nowIso() });
        await writeJsonFile(file, normalized);
        return normalized;
    });
}
export async function recordRunBudgetUsage(store, runId, input) {
    return updateRunBudget(store, runId, (state) => {
        const model = input.model ?? "unknown";
        const tokens = tokenUsageFromUnknown(input.token_usage);
        return {
            ...state,
            usage: {
                child_sessions: state.usage.child_sessions + Math.max(0, Math.floor(input.child_sessions ?? 0)),
                model_calls: state.usage.model_calls + Math.max(0, Math.floor(input.model_calls ?? 0)),
                tokens_by_model: tokens
                    ? {
                        ...state.usage.tokens_by_model,
                        [model]: addTokenUsage(state.usage.tokens_by_model[model], tokens),
                    }
                    : state.usage.tokens_by_model,
                context: state.usage.context ?? emptyContextUsage(),
            },
        };
    });
}
export async function recordRunBudgetContextInjection(store, runId, input) {
    return updateRunBudget(store, runId, (state) => {
        const current = state.usage.context ?? emptyContextUsage();
        const byCategory = { ...current.injected_chars_by_category };
        const duplicates = { ...current.duplicate_bundle_injections };
        for (const attachment of input.attachments ?? []) {
            byCategory[attachment.category] = (byCategory[attachment.category] ?? 0) + Math.max(0, Math.floor(attachment.char_count ?? 0));
            duplicates[attachment.bundle_id] = (duplicates[attachment.bundle_id] ?? 0) + 1;
        }
        return {
            ...state,
            usage: {
                ...state.usage,
                context: {
                    injected_chars_total: current.injected_chars_total + Math.max(0, Math.floor(input.injected_chars ?? 0)),
                    injected_chars_by_category: byCategory,
                    duplicate_bundle_injections: duplicates,
                },
            },
        };
    });
}
function normalizeRunBudget(store, runId, raw) {
    const created = typeof raw.created_at === "string" ? raw.created_at : nowIso();
    return {
        schema_version: BUDGET_SCHEMA_VERSION,
        run_id: typeof raw.run_id === "string" ? raw.run_id : runId,
        created_at: created,
        updated_at: typeof raw.updated_at === "string" ? raw.updated_at : created,
        limits: {
            max_runtime_hours: positiveNumber(raw.limits?.max_runtime_hours ?? store.config.budget.max_runtime_hours, 100),
            max_child_sessions: positiveInteger(raw.limits?.max_child_sessions ?? store.config.budget.max_child_sessions, 64),
            max_model_calls: positiveInteger(raw.limits?.max_model_calls ?? store.config.budget.max_model_calls, 1000),
            warn_ratio: clampRatio(raw.limits?.warn_ratio ?? store.config.budget.warn_ratio, 0.8),
            stale_worker_minutes: positiveNumber(raw.limits?.stale_worker_minutes ?? store.config.budget.stale_worker_minutes, 30),
        },
        usage: {
            child_sessions: Math.max(0, Math.floor(raw.usage?.child_sessions ?? 0)),
            model_calls: Math.max(0, Math.floor(raw.usage?.model_calls ?? 0)),
            tokens_by_model: normalizeTokens(raw.usage?.tokens_by_model),
            context: normalizeContextUsage(raw.usage?.context),
        },
        first_warning_at: typeof raw.first_warning_at === "string" ? raw.first_warning_at : undefined,
        blocking_reason: typeof raw.blocking_reason === "string" ? raw.blocking_reason : undefined,
    };
}
function emptyContextUsage() {
    return {
        injected_chars_total: 0,
        injected_chars_by_category: {},
        duplicate_bundle_injections: {},
    };
}
function normalizeContextUsage(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return emptyContextUsage();
    const record = raw;
    const byCategoryRaw = record.injected_chars_by_category;
    const byCategory = typeof byCategoryRaw === "object" && byCategoryRaw !== null && !Array.isArray(byCategoryRaw)
        ? Object.fromEntries(Object.entries(byCategoryRaw).map(([key, value]) => [key, Math.max(0, Math.floor(numeric(value)))]))
        : {};
    const duplicateRaw = record.duplicate_bundle_injections;
    const duplicates = typeof duplicateRaw === "object" && duplicateRaw !== null && !Array.isArray(duplicateRaw)
        ? Object.fromEntries(Object.entries(duplicateRaw).map(([key, value]) => [key, Math.max(0, Math.floor(numeric(value)))]))
        : {};
    return {
        injected_chars_total: Math.max(0, Math.floor(numeric(record.injected_chars_total))),
        injected_chars_by_category: byCategory,
        duplicate_bundle_injections: duplicates,
    };
}
function normalizeTokens(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return {};
    const output = {};
    for (const [model, value] of Object.entries(raw)) {
        const tokens = tokenUsageFromUnknown(value);
        if (tokens)
            output[model] = tokens;
    }
    return output;
}
function tokenUsageFromUnknown(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw))
        return undefined;
    const record = raw;
    const input = numeric(record.input) + numeric(record.prompt);
    const output = numeric(record.output) + numeric(record.completion);
    const reasoning = numeric(record.reasoning);
    const total = numeric(record.total) || input + output + reasoning;
    if (input + output + reasoning + total <= 0)
        return undefined;
    return { input, output, reasoning, total };
}
function addTokenUsage(left, right) {
    return {
        input: (left?.input ?? 0) + right.input,
        output: (left?.output ?? 0) + right.output,
        reasoning: (left?.reasoning ?? 0) + right.reasoning,
        total: (left?.total ?? 0) + right.total,
    };
}
function numeric(value) {
    return typeof value === "number" && Number.isFinite(value) ? value : 0;
}
function positiveInteger(value, fallback) {
    return Math.max(1, Math.floor(typeof value === "number" && Number.isFinite(value) ? value : fallback));
}
function positiveNumber(value, fallback) {
    return Math.max(0.001, typeof value === "number" && Number.isFinite(value) ? value : fallback);
}
function clampRatio(value, fallback) {
    const numericValue = typeof value === "number" && Number.isFinite(value) ? value : fallback;
    return Math.min(1, Math.max(0.01, numericValue));
}
//# sourceMappingURL=store.js.map