// engine/escalator/with-escalation.ts — SPEC-729
// Generic retry wrapper with bounded budgets and escalation sentinel.
import { nextDecision } from './policy.js';
import { incrementCounter, resetCounter } from './retry-counter.js';
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Wraps a retryable flow with bounded retry logic.
 *
 * Behaviour:
 * - Calls `fn` in a loop.
 * - On `ok: true`, returns the result with attempt count.
 * - On `ok: false`, increments the retry counter.
 * - When `nextDecision(role, attempts) === 'escalate'`, returns an escalation sentinel.
 * - The wrapper does NOT pause for user input — it returns a sentinel that the caller
 *   is responsible for forwarding as `interactiveQuestions`.
 *
 * The `reviewerToken` in `opts` is preserved across retries (SPEC-722).
 *
 * @param opts - Escalation options (role, specId, frontmatterSha, projectId, reviewerToken).
 * @param fn   - The async function to retry. Must return `{ ok, result }` or `{ ok, reason }`.
 */
export async function withEscalation(opts, fn) {
    const { role, specId, frontmatterSha, projectId, reviewerToken } = opts;
    const retryKey = { role, specId, frontmatterSha };
    let attempts = 0;
    let lastReason;
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- infinite loop is intentional, controlled by return statements
    while (true) {
        attempts++;
        // Pass the reviewerToken through unchanged on every attempt (SPEC-722)
        const flowResult = await fn(attempts, reviewerToken);
        if (flowResult.ok) {
            // Success: reset counter and return
            await resetCounter(projectId, retryKey).catch(() => {
                // Non-fatal — counter cleanup failure doesn't affect the result
            });
            return { ok: true, result: flowResult.result, attempts };
        }
        // Failure: record the reason and check budget
        lastReason = flowResult.reason;
        const count = await incrementCounter(projectId, retryKey);
        const decision = nextDecision(role, count);
        if (decision === 'escalate') {
            // Budget exhausted — return escalation sentinel.
            // Do NOT reset counter here; the caller resets after user decision.
            // Pick the default escalation decision: abort for releaser, retry for others.
            const escalateDecision = role === 'releaser' ? 'abort' : 'retry';
            return {
                ok: false,
                escalated: true,
                decision: escalateDecision,
                lastReason,
            };
        }
        // Continue loop for next retry
    }
}
//# sourceMappingURL=with-escalation.js.map