/**
 * Generic retry utility with exponential backoff
 *
 * Provides configurable retry logic for any async operation with:
 * - Exponential backoff
 * - Jitter to prevent thundering herd
 * - Customizable retry conditions
 * - Timeout support
 * - Event callbacks
 */
const DEFAULT_OPTIONS = {
    maxRetries: 3,
    baseDelay: 1000,
    maxDelay: 30000,
    backoffFactor: 2,
    jitter: true,
    isRetryable: () => true,
};
/**
 * Calculate delay with exponential backoff and optional jitter
 */
function calculateDelay(attempt, baseDelay, maxDelay, backoffFactor, jitter) {
    const exponentialDelay = baseDelay * Math.pow(backoffFactor, attempt);
    const clampedDelay = Math.min(exponentialDelay, maxDelay);
    if (jitter) {
        // Add 0-25% random jitter
        const jitterAmount = clampedDelay * 0.25 * Math.random();
        return clampedDelay + jitterAmount;
    }
    return clampedDelay;
}
/**
 * Sleep utility that respects abort signal
 */
function sleep(ms, signal) {
    return new Promise((resolve, reject) => {
        if (signal?.aborted) {
            reject(new Error('Operation aborted'));
            return;
        }
        const timeoutId = setTimeout(resolve, ms);
        signal?.addEventListener('abort', () => {
            clearTimeout(timeoutId);
            reject(new Error('Operation aborted'));
        });
    });
}
/**
 * Execute a function with retry logic
 *
 * @param fn - The async function to retry
 * @param options - Retry configuration options
 * @returns Promise with the function result
 *
 * @example
 * ```typescript
 * const result = await retry(
 *   async () => fetch('https://api.example.com/data'),
 *   {
 *     maxRetries: 5,
 *     baseDelay: 500,
 *     isRetryable: (err) => err.message.includes('timeout'),
 *     onRetry: (err, attempt, delay) => {
 *       console.log(`Retry ${attempt} after ${delay}ms`);
 *     }
 *   }
 * );
 * ```
 */
export async function retry(fn, options = {}) {
    const opts = { ...DEFAULT_OPTIONS, ...options };
    const startTime = Date.now();
    let lastError;
    for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
        // Check abort signal
        if (opts.signal?.aborted) {
            throw new Error('Operation aborted');
        }
        // Check timeout
        if (opts.timeout && Date.now() - startTime > opts.timeout) {
            throw new Error(`Retry timeout exceeded after ${opts.timeout}ms`);
        }
        try {
            const result = await fn();
            opts.onSuccess?.(result, attempt + 1);
            return result;
        }
        catch (error) {
            lastError = error;
            // Check if we should retry
            const isLastAttempt = attempt === opts.maxRetries;
            const isRetryable = opts.isRetryable(error);
            if (isLastAttempt || !isRetryable) {
                opts.onFailed?.(error, attempt + 1);
                throw error;
            }
            // Calculate delay and wait
            const delay = calculateDelay(attempt, opts.baseDelay, opts.maxDelay, opts.backoffFactor, opts.jitter);
            opts.onRetry?.(error, attempt + 1, delay);
            await sleep(delay, opts.signal);
        }
    }
    // This should never be reached, but TypeScript needs it
    throw lastError;
}
/**
 * Execute with retry and return a result object instead of throwing
 */
export async function retryWithResult(fn, options = {}) {
    const startTime = Date.now();
    let attempts = 0;
    try {
        const modifiedOptions = {
            ...options,
            onRetry: (error, attempt, delay) => {
                attempts = attempt;
                options.onRetry?.(error, attempt, delay);
            },
            onSuccess: (result, attemptCount) => {
                attempts = attemptCount;
                options.onSuccess?.(result, attemptCount);
            },
        };
        const result = await retry(fn, modifiedOptions);
        return {
            success: true,
            result,
            attempts,
            totalTime: Date.now() - startTime,
        };
    }
    catch (error) {
        return {
            success: false,
            error: error instanceof Error ? error : new Error(String(error)),
            attempts,
            totalTime: Date.now() - startTime,
        };
    }
}
/**
 * Create a retryable version of a function
 */
export function withRetry(fn, options = {}) {
    return (...args) => retry(() => fn(...args), options);
}
/**
 * Retry decorator for class methods
 */
export function Retry(options = {}) {
    return function (_target, _propertyKey, descriptor) {
        const originalMethod = descriptor.value;
        if (!originalMethod)
            return descriptor;
        descriptor.value = async function (...args) {
            return retry(() => originalMethod.apply(this, args), options);
        };
        return descriptor;
    };
}
/**
 * Common retry predicates for specific error types
 */
export const RetryPredicates = {
    /** Retry on network errors */
    networkError: (error) => {
        const message = error instanceof Error ? error.message.toLowerCase() : '';
        return (message.includes('network') ||
            message.includes('timeout') ||
            message.includes('econnreset') ||
            message.includes('econnrefused') ||
            message.includes('enotfound') ||
            message.includes('etimedout') ||
            message.includes('epipe') ||
            message.includes('econnaborted') ||
            message.includes('socket') ||
            message.includes('fetch failed'));
    },
    /** Retry on HTTP 5xx errors */
    serverError: (error) => {
        const message = error instanceof Error ? error.message.toLowerCase() : '';
        // Check for status code in error object
        const status = error?.status;
        if (status && status >= 500 && status < 600)
            return true;
        return (message.includes('500') ||
            message.includes('502') ||
            message.includes('503') ||
            message.includes('504') ||
            message.includes('internal server error') ||
            message.includes('bad gateway') ||
            message.includes('service unavailable') ||
            message.includes('gateway timeout'));
    },
    /** Retry on rate limit errors (429) */
    rateLimitError: (error) => {
        const message = error instanceof Error ? error.message.toLowerCase() : '';
        // Check for status code in error object
        const status = error?.status;
        if (status === 429)
            return true;
        return (message.includes('429') ||
            message.includes('rate limit') ||
            message.includes('too many requests') ||
            message.includes('throttl') ||
            message.includes('quota exceeded'));
    },
    /** Retry on OpenAI/LLM API specific errors */
    llmApiError: (error) => {
        const message = error instanceof Error ? error.message.toLowerCase() : '';
        const status = error?.status;
        // Retryable status codes for LLM APIs
        if (status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {
            return true;
        }
        return (RetryPredicates.networkError(error) ||
            RetryPredicates.serverError(error) ||
            RetryPredicates.rateLimitError(error) ||
            message.includes('overloaded') ||
            message.includes('capacity') ||
            message.includes('temporarily unavailable') ||
            message.includes('api error') ||
            message.includes('request failed'));
    },
    /** Retry on cloud storage errors */
    cloudStorageError: (error) => {
        const message = error instanceof Error ? error.message.toLowerCase() : '';
        const status = error?.status;
        // Retryable status codes for cloud storage
        if (status === 408 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504) {
            return true;
        }
        return (RetryPredicates.networkError(error) ||
            RetryPredicates.serverError(error) ||
            message.includes('request timeout') ||
            message.includes('slow down') ||
            message.includes('service unavailable'));
    },
    /** Retry on any transient error */
    transientError: (error) => {
        return (RetryPredicates.networkError(error) ||
            RetryPredicates.serverError(error) ||
            RetryPredicates.rateLimitError(error));
    },
    /** Never retry (for explicit no-retry scenarios) */
    never: () => false,
    /** Always retry (default behavior) */
    always: () => true,
};
/**
 * Pre-configured retry strategies
 */
export const RetryStrategies = {
    /** Fast retries for local operations */
    fast: {
        maxRetries: 3,
        baseDelay: 100,
        maxDelay: 1000,
        backoffFactor: 2,
    },
    /** Standard retries for API calls */
    standard: {
        maxRetries: 3,
        baseDelay: 1000,
        maxDelay: 10000,
        backoffFactor: 2,
        isRetryable: RetryPredicates.transientError,
    },
    /** Aggressive retries for critical operations */
    aggressive: {
        maxRetries: 5,
        baseDelay: 500,
        maxDelay: 30000,
        backoffFactor: 2,
        isRetryable: RetryPredicates.transientError,
    },
    /** Patient retries for rate-limited APIs */
    patient: {
        maxRetries: 10,
        baseDelay: 2000,
        maxDelay: 60000,
        backoffFactor: 2,
        isRetryable: RetryPredicates.rateLimitError,
    },
    /** LLM API retries (Grok, OpenAI, Anthropic, etc.) */
    llmApi: {
        maxRetries: 3,
        baseDelay: 1000,
        maxDelay: 30000,
        backoffFactor: 2,
        jitter: true,
        isRetryable: RetryPredicates.llmApiError,
    },
    /** Cloud storage retries (S3, GCS, Azure) */
    cloudStorage: {
        maxRetries: 5,
        baseDelay: 500,
        maxDelay: 15000,
        backoffFactor: 2,
        jitter: true,
        isRetryable: RetryPredicates.cloudStorageError,
    },
    /** Web search/fetch retries */
    webRequest: {
        maxRetries: 3,
        baseDelay: 500,
        maxDelay: 5000,
        backoffFactor: 2,
        jitter: true,
        isRetryable: RetryPredicates.transientError,
    },
    /** No retries */
    none: {
        maxRetries: 0,
    },
};
//# sourceMappingURL=retry.js.map