/**
 * Retry and Backoff Utilities
 *
 * Shared utilities for implementing resilient retry logic with exponential backoff
 * and jitter. Used by both polling and batch operations.
 */
/**
 * Maximum number of consecutive errors before giving up
 * Prevents infinite retry loops when the API is consistently failing
 */
export const MAX_CONSECUTIVE_ERRORS = 3;
/**
 * Base delay for error backoff in milliseconds
 * Each error adds this amount (with scaling) to the wait time
 */
export const BASE_ERROR_BACKOFF_MS = 1000;
/**
 * Base delay for exponential backoff in milliseconds
 */
export const BASE_EXPONENTIAL_BACKOFF_MS = 1000;
/**
 * Jitter factor (0.0 to 1.0) for randomizing wait times
 * Prevents thundering herd problem when many clients retry simultaneously
 * A factor of 0.5 means we add 0-50% random variance to the base interval
 */
export const JITTER_FACTOR = 0.5;
/**
 * Calculate wait time with jitter and error backoff
 *
 * This implements two key reliability patterns:
 * 1. Jitter - Adds randomness to prevent synchronized retries across clients
 * 2. Error backoff - Increases wait time when errors occur, giving the API time to recover
 *
 * @param baseInterval - The base wait time in milliseconds
 * @param errorCount - Number of consecutive errors (0 if no errors)
 * @returns Wait time in milliseconds with jitter and error backoff applied
 *
 * @example
 * // No errors: returns 1000-1500ms (1000 + 0-500ms jitter)
 * calculateErrorBackoffMs(1000, 0);
 *
 * // 2 errors: returns 1000-1500ms base + up to 1000ms error backoff = 1000-2500ms
 * calculateErrorBackoffMs(1000, 2);
 *
 * // 6 errors: returns 1000-1500ms base + 2000ms capped backoff = 3000-3500ms
 * calculateErrorBackoffMs(1000, 6);
 */
export function calculateErrorBackoffMs(baseInterval, errorCount) {
    // Jitter to avoid thundering herd (multiple clients retrying at exact same time)
    const jitter = Math.random() * JITTER_FACTOR * baseInterval;
    // Exponential backoff scaled by error count, but capped at 2x base interval
    // This prevents wait times from growing unboundedly
    const errorBackoff = Math.min(BASE_ERROR_BACKOFF_MS * (errorCount / 2), baseInterval * 2);
    return Math.floor(baseInterval + jitter + errorBackoff);
}
/**
 * Calculate exponential backoff delay with jitter
 *
 * @param attempt - The attempt number (1-based)
 * @param baseDelayMs - Base delay in milliseconds (default: 1000)
 * @returns Delay in milliseconds: baseDelayMs * 2^(attempt-1) + jitter
 *
 * @example
 * calculateExponentialBackoffMs(1) // ~1000-1500ms
 * calculateExponentialBackoffMs(2) // ~2000-3000ms
 * calculateExponentialBackoffMs(3) // ~4000-6000ms
 */
export function calculateExponentialBackoffMs(attempt, baseDelayMs = BASE_EXPONENTIAL_BACKOFF_MS) {
    const baseDelay = baseDelayMs * Math.pow(2, attempt - 1);
    const jitter = Math.random() * JITTER_FACTOR * baseDelay;
    return Math.floor(baseDelay + jitter);
}
/**
 * Sleep for a specified number of milliseconds. If `signal` is provided
 * and aborts during the sleep, resolves early. Resolves rather than
 * rejecting on abort so callers don't need to wrap each sleep in a
 * try/catch — the next operation in the loop is the one that should
 * surface the cancellation (typically a fetch with the same signal).
 */
export function sleep(ms, signal) {
    if (!signal) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
    if (signal.aborted)
        return Promise.resolve();
    return new Promise((resolve) => {
        const timer = setTimeout(() => {
            signal.removeEventListener("abort", onAbort);
            resolve();
        }, ms);
        const onAbort = () => {
            clearTimeout(timer);
            resolve();
        };
        signal.addEventListener("abort", onAbort, { once: true });
    });
}
