import { AppError, RETRYABLE_STATUS_CODES } from '../types/errors.js';
import { summarizeError } from './error-display.js';
const DEFAULT_CONFIG = {
    maxRetries: 3,
    initialDelayMs: 1000,
    maxDelayMs: 30000,
};
/** Type guard: checks if an unknown value has a numeric `statusCode` property. */
export function hasStatusCode(err) {
    return (typeof err === 'object' &&
        err !== null &&
        'statusCode' in err &&
        typeof err.statusCode === 'number');
}
/** Type guard: checks if an unknown value has a numeric `status` property. */
export function hasStatus(err) {
    return (typeof err === 'object' &&
        err !== null &&
        'status' in err &&
        typeof err.status === 'number');
}
/**
 * Determines whether an error should trigger a retry.
 * Checks AppError.recoverable first, then falls back to HTTP status code inspection.
 * Handles both Error subclasses and plain objects with statusCode/status properties.
 */
export function isRetryableError(error) {
    // AppError with explicit recoverability flag takes priority
    if (error instanceof AppError) {
        return error.recoverable;
    }
    // Inspect statusCode / status on any object (including non-Error throwables)
    if (hasStatusCode(error)) {
        return RETRYABLE_STATUS_CODES.includes(error.statusCode);
    }
    if (hasStatus(error)) {
        return RETRYABLE_STATUS_CODES.includes(error.status);
    }
    return false;
}
function computeDelay(attempt, initialDelayMs, maxDelayMs) {
    // Exponential backoff: initialDelay * 2^attempt, with jitter
    const exponential = initialDelayMs * Math.pow(2, attempt);
    const jitter = Math.random() * initialDelayMs;
    return Math.min(exponential + jitter, maxDelayMs);
}
/**
 * Wraps an async function with retry logic using exponential backoff.
 * Retries when `isRetryableError` returns true for the thrown error.
 *
 * @param fn - Async function to execute.
 * @param config - Optional overrides for retry behaviour.
 * @param onRetry - Optional callback invoked before each retry.
 */
export async function withRetry(fn, config, onRetry) {
    const cfg = { ...DEFAULT_CONFIG, ...config };
    let lastError = new Error('Unknown error');
    for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
        try {
            return await fn();
        }
        catch (err) {
            lastError = err instanceof Error ? err : new Error(summarizeError(err));
            if (attempt === cfg.maxRetries) {
                break;
            }
            if (!isRetryableError(err)) {
                throw lastError;
            }
            const delayMs = computeDelay(attempt, cfg.initialDelayMs, cfg.maxDelayMs);
            onRetry?.(attempt + 1, lastError, delayMs);
            await new Promise((resolve) => setTimeout(resolve, delayMs));
        }
    }
    throw lastError;
}
