"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackfillRetryExhaustedError = void 0;
exports.isRetryableBackfillError = isRetryableBackfillError;
exports.resolveBackfillRetryConfig = resolveBackfillRetryConfig;
exports.retryWithExponentialBackoff = retryWithExponentialBackoff;
const logger_1 = __importDefault(require("../../../logger"));
const DEFAULT_RETRY_CONFIG = {
    maxRetries: 5,
    initialDelayMs: 500,
    maxDelayMs: 30000,
    backoffMultiplier: 2,
};
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
function errorMessage(error) {
    if (error instanceof Error) {
        return error.message;
    }
    return String(error);
}
function errorStatusCode(error) {
    if (error === null || typeof error !== 'object') {
        return undefined;
    }
    const e = error;
    const raw = e.status ?? e.statusCode ?? e.code;
    if (typeof raw === 'number' && Number.isFinite(raw)) {
        return raw;
    }
    if (typeof raw === 'string' && /^\d+$/.test(raw)) {
        return Number(raw);
    }
    return undefined;
}
/** Transient failures worth retrying during long backfill runs. */
function isRetryableBackfillError(error) {
    if (!error) {
        return false;
    }
    const status = errorStatusCode(error);
    if (status !== undefined && [408, 429, 500, 502, 503, 504].includes(status)) {
        return true;
    }
    const msg = errorMessage(error).toLowerCase();
    const retryablePatterns = [
        'timeout',
        'timed out',
        'econnrefused',
        'econnreset',
        'etimedout',
        'enotfound',
        'eai_again',
        'socket hang up',
        'connection closed',
        'connection reset',
        'network error',
        'service unavailable',
        'bad gateway',
        'gateway timeout',
        'too many requests',
        'rate limit',
        'temporarily unavailable',
        'connection pool',
    ];
    return retryablePatterns.some((pattern) => msg.includes(pattern));
}
function resolveBackfillRetryConfig(options) {
    const maxRetriesEnv = Number(process.env.EVAL_BACKFILL_MAX_RETRIES);
    const initialEnv = Number(process.env.EVAL_BACKFILL_RETRY_INITIAL_MS);
    const maxDelayEnv = Number(process.env.EVAL_BACKFILL_RETRY_MAX_MS);
    const multiplierEnv = Number(process.env.EVAL_BACKFILL_RETRY_MULTIPLIER);
    return {
        maxRetries: Math.max(0, Math.floor(options?.maxRetries ??
            (Number.isFinite(maxRetriesEnv) ? maxRetriesEnv : DEFAULT_RETRY_CONFIG.maxRetries))),
        initialDelayMs: Math.max(0, Math.floor(options?.initialDelayMs ??
            (Number.isFinite(initialEnv) ? initialEnv : DEFAULT_RETRY_CONFIG.initialDelayMs))),
        maxDelayMs: Math.max(0, Math.floor(options?.maxDelayMs ??
            (Number.isFinite(maxDelayEnv) ? maxDelayEnv : DEFAULT_RETRY_CONFIG.maxDelayMs))),
        backoffMultiplier: Math.max(1, options?.backoffMultiplier ??
            (Number.isFinite(multiplierEnv) ? multiplierEnv : DEFAULT_RETRY_CONFIG.backoffMultiplier)),
    };
}
class BackfillRetryExhaustedError extends Error {
    constructor(label, attempts, cause) {
        super(`Backfill operation "${label}" failed after ${attempts} attempt(s): ${errorMessage(cause)}`);
        this.name = 'BackfillRetryExhaustedError';
        this.label = label;
        this.attempts = attempts;
        this.cause = cause;
    }
}
exports.BackfillRetryExhaustedError = BackfillRetryExhaustedError;
async function retryWithExponentialBackoff(operation, params) {
    const isRetryable = params.isRetryable ?? isRetryableBackfillError;
    const maxAttempts = params.config.maxRetries + 1;
    let delay = params.config.initialDelayMs;
    let lastError;
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            return await operation();
        }
        catch (error) {
            lastError = error;
            if (attempt >= maxAttempts || !isRetryable(error)) {
                if (attempt >= maxAttempts && isRetryable(error)) {
                    throw new BackfillRetryExhaustedError(params.label, attempt, error);
                }
                throw error;
            }
            const waitMs = Math.min(delay, params.config.maxDelayMs);
            params.onRetry?.(attempt, waitMs, error);
            logger_1.default.warn(`backfill retry [${params.label}] attempt ${attempt}/${maxAttempts - 1} failed: ${errorMessage(error)}; retrying in ${waitMs}ms`);
            await sleep(waitMs);
            delay = Math.min(delay * params.config.backoffMultiplier, params.config.maxDelayMs);
        }
    }
    throw new BackfillRetryExhaustedError(params.label, maxAttempts, lastError);
}
//# sourceMappingURL=backfillRetry.js.map