// engine/timing/budget.ts — Per-step budget wrapper (SPEC-713)
// withBudget(stepName, ms, fn) aborts fn after `ms` milliseconds and returns
// a PartialResult with a warning string instead of throwing.
/**
 * Run `fn` within a time budget of `budgetMs` milliseconds.
 *
 * - If `fn` resolves in time → returns {exceeded: false, value, elapsedMs}
 * - If the budget fires first → returns {exceeded: true, step, budgetMs, elapsedMs, warning}
 *   and the underlying promise is left to settle on its own (fire-and-forget).
 *
 * Never throws — all errors from `fn` are caught and treated as budget exceeded.
 */
export async function withBudget(step, budgetMs, fn) {
    const start = performance.now();
    const timeoutPromise = new Promise((resolve) => {
        setTimeout(() => {
            const elapsed = Math.round(performance.now() - start);
            resolve({
                exceeded: true,
                step,
                budgetMs,
                elapsedMs: elapsed,
                warning: `[SPEC-713] Step "${step}" exceeded ${String(budgetMs)}ms budget (actual: ${String(elapsed)}ms)`,
            });
        }, budgetMs);
    });
    try {
        const result = await Promise.race([
            fn().then((value) => ({
                exceeded: false,
                value,
                elapsedMs: Math.round(performance.now() - start),
            })),
            timeoutPromise,
        ]);
        return result;
    }
    catch (err) {
        const elapsed = Math.round(performance.now() - start);
        const message = err instanceof Error ? err.message : String(err);
        return {
            exceeded: true,
            step,
            budgetMs,
            elapsedMs: elapsed,
            warning: `[SPEC-713] Step "${step}" threw after ${String(elapsed)}ms: ${message}`,
        };
    }
}
/**
 * Extract the value from a BudgetResult, or return `fallback` when exceeded.
 * Optionally collect warnings into a mutable array.
 */
export function unwrapBudget(result, fallback, warnings) {
    if (result.exceeded) {
        warnings?.push(result.warning);
        return fallback;
    }
    return result.value;
}
/**
 * Total budget enforcer — wraps an entire operation in a hard ceiling.
 * Returns {timedOut: true, warning} if the ceiling fires, or {timedOut: false, value} otherwise.
 */
export async function withTotalBudget(operationName, ceilingMs, fn) {
    const start = performance.now();
    const ceilingPromise = new Promise((resolve) => {
        setTimeout(() => {
            const elapsed = Math.round(performance.now() - start);
            resolve({
                timedOut: true,
                warning: `[SPEC-713] Operation "${operationName}" exceeded ${String(ceilingMs)}ms ceiling (actual: ${String(elapsed)}ms) — aborting cleanly`,
            });
        }, ceilingMs);
    });
    try {
        return await Promise.race([
            fn().then((value) => ({ timedOut: false, value })),
            ceilingPromise,
        ]);
    }
    catch (err) {
        const elapsed = Math.round(performance.now() - start);
        const message = err instanceof Error ? err.message : String(err);
        return {
            timedOut: true,
            warning: `[SPEC-713] Operation "${operationName}" threw after ${String(elapsed)}ms: ${message}`,
        };
    }
}
//# sourceMappingURL=budget.js.map