"use strict";
/**
 * Retry Manager for Transient Failures
 * Handles automatic retries with exponential backoff and intelligent failure detection
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RetryHelpers = exports.RetryError = exports.CircuitBreaker = exports.RetryManager = exports.FailureType = exports.RetryStrategy = void 0;
var RetryStrategy;
(function (RetryStrategy) {
    RetryStrategy["EXPONENTIAL_BACKOFF"] = "exponential_backoff";
    RetryStrategy["LINEAR_BACKOFF"] = "linear_backoff";
    RetryStrategy["IMMEDIATE"] = "immediate";
    RetryStrategy["CUSTOM"] = "custom";
})(RetryStrategy || (exports.RetryStrategy = RetryStrategy = {}));
var FailureType;
(function (FailureType) {
    FailureType["TRANSIENT"] = "transient";
    FailureType["PERMANENT"] = "permanent";
    FailureType["UNKNOWN"] = "unknown";
})(FailureType || (exports.FailureType = FailureType = {}));
/**
 * Transient error patterns
 */
const TRANSIENT_ERROR_PATTERNS = [
    // Network errors
    /ECONNREFUSED/i,
    /ECONNRESET/i,
    /ETIMEDOUT/i,
    /ENETUNREACH/i,
    /EAI_AGAIN/i,
    // File system errors
    /EBUSY/i,
    /EAGAIN/i,
    /EMFILE/i,
    /ENFILE/i,
    // Service errors
    /503/i,
    /429/i, // Rate limit
    /502/i,
    /504/i,
    // Database errors
    /connection refused/i,
    /connection reset/i,
    /deadlock/i,
    /lock timeout/i,
    // Generic
    /temporary failure/i,
    /try again/i,
];
/**
 * Permanent error patterns
 */
const PERMANENT_ERROR_PATTERNS = [
    // Authentication
    /401/i,
    /403/i,
    /unauthorized/i,
    /forbidden/i,
    // Not found
    /404/i,
    /not found/i,
    /does not exist/i,
    // Validation
    /400/i,
    /invalid/i,
    /malformed/i,
    /syntax error/i,
    // Conflicts
    /409/i,
    /conflict/i,
    /already exists/i,
];
class RetryManager {
    static DEFAULT_CONFIG = {
        maxAttempts: 3,
        strategy: RetryStrategy.EXPONENTIAL_BACKOFF,
        initialDelay: 1000,
        maxDelay: 30000,
        backoffMultiplier: 2,
        timeout: 60000,
    };
    /**
     * Execute a function with automatic retry logic
     */
    static async execute(fn, config = {}) {
        const finalConfig = { ...this.DEFAULT_CONFIG, ...config };
        const startTime = Date.now();
        let lastError;
        let attempt = 0;
        while (attempt < finalConfig.maxAttempts) {
            attempt++;
            try {
                // Execute with timeout if configured
                const result = finalConfig.timeout
                    ? await this.withTimeout(fn(), finalConfig.timeout)
                    : await fn();
                return {
                    success: true,
                    result,
                    attempts: attempt,
                    totalTime: Date.now() - startTime,
                };
            }
            catch (error) {
                lastError = error instanceof Error ? error : new Error(String(error));
                // Check if we should retry
                const failureType = this.classifyFailure(lastError);
                if (failureType === FailureType.PERMANENT) {
                    return {
                        success: false,
                        error: lastError,
                        attempts: attempt,
                        totalTime: Date.now() - startTime,
                        failureType,
                    };
                }
                // Check custom shouldRetry if provided
                if (finalConfig.shouldRetry && !finalConfig.shouldRetry(lastError)) {
                    return {
                        success: false,
                        error: lastError,
                        attempts: attempt,
                        totalTime: Date.now() - startTime,
                        failureType,
                    };
                }
                // Call retry callback if provided
                if (finalConfig.onRetry) {
                    finalConfig.onRetry(attempt, lastError);
                }
                // Don't delay after last attempt
                if (attempt < finalConfig.maxAttempts) {
                    const delay = this.calculateDelay(attempt, finalConfig);
                    await this.sleep(delay);
                }
            }
        }
        return {
            success: false,
            error: lastError,
            attempts: attempt,
            totalTime: Date.now() - startTime,
            failureType: lastError ? this.classifyFailure(lastError) : FailureType.UNKNOWN,
        };
    }
    /**
     * Classify error as transient or permanent
     */
    static classifyFailure(error) {
        const message = error.message;
        // Check for permanent errors first
        for (const pattern of PERMANENT_ERROR_PATTERNS) {
            if (pattern.test(message)) {
                return FailureType.PERMANENT;
            }
        }
        // Check for transient errors
        for (const pattern of TRANSIENT_ERROR_PATTERNS) {
            if (pattern.test(message)) {
                return FailureType.TRANSIENT;
            }
        }
        // If we can't classify, assume it might be transient
        return FailureType.UNKNOWN;
    }
    /**
     * Calculate delay based on retry strategy
     */
    static calculateDelay(attempt, config) {
        const { strategy, initialDelay = 1000, maxDelay = 30000, backoffMultiplier = 2 } = config;
        let delay;
        switch (strategy) {
            case RetryStrategy.EXPONENTIAL_BACKOFF:
                delay = initialDelay * Math.pow(backoffMultiplier, attempt - 1);
                break;
            case RetryStrategy.LINEAR_BACKOFF:
                delay = initialDelay * attempt;
                break;
            case RetryStrategy.IMMEDIATE:
                delay = 0;
                break;
            case RetryStrategy.CUSTOM:
                delay = initialDelay;
                break;
            default:
                delay = initialDelay;
        }
        // Add jitter to prevent thundering herd
        const jitter = Math.random() * 0.1 * delay;
        delay += jitter;
        // Cap at max delay
        return Math.min(delay, maxDelay);
    }
    /**
     * Execute function with timeout
     */
    static withTimeout(promise, timeout) {
        let timeoutId;
        const timeoutPromise = new Promise((_, reject) => {
            timeoutId = setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout);
        });
        return Promise.race([
            promise.finally(() => clearTimeout(timeoutId)),
            timeoutPromise,
        ]);
    }
    /**
     * Sleep for specified milliseconds
     */
    static sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    /**
     * Batch retry - execute multiple operations with retry
     */
    static async executeBatch(operations, config = {}) {
        const results = [];
        const startTime = Date.now();
        for (let i = 0; i < operations.length; i++) {
            const result = await this.execute(operations[i], config);
            results.push(result);
            // Stop if we hit a permanent failure (unless configured otherwise)
            if (!result.success && result.failureType === FailureType.PERMANENT) {
                break;
            }
        }
        const succeeded = results.filter(r => r.success);
        const failed = results.filter(r => !r.success);
        return {
            totalOperations: operations.length,
            executedOperations: results.length,
            succeeded: succeeded.length,
            failed: failed.length,
            results,
            totalTime: Date.now() - startTime,
            allSucceeded: succeeded.length === operations.length,
        };
    }
    /**
     * Circuit breaker pattern - stop retrying if too many failures
     */
    static createCircuitBreaker(name, failureThreshold = 5, resetTimeout = 60000) {
        return new CircuitBreaker(name, failureThreshold, resetTimeout);
    }
}
exports.RetryManager = RetryManager;
/**
 * Circuit breaker implementation
 */
class CircuitBreaker {
    name;
    failureThreshold;
    resetTimeout;
    failures = 0;
    lastFailureTime = 0;
    state = 'closed';
    constructor(name, failureThreshold, resetTimeout) {
        this.name = name;
        this.failureThreshold = failureThreshold;
        this.resetTimeout = resetTimeout;
    }
    async execute(fn) {
        if (this.state === 'open') {
            if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
                this.state = 'half-open';
            }
            else {
                throw new Error(`Circuit breaker '${this.name}' is OPEN - too many failures`);
            }
        }
        try {
            const result = await fn();
            this.onSuccess();
            return result;
        }
        catch (error) {
            this.onFailure();
            throw error;
        }
    }
    onSuccess() {
        this.failures = 0;
        this.state = 'closed';
    }
    onFailure() {
        this.failures++;
        this.lastFailureTime = Date.now();
        if (this.failures >= this.failureThreshold) {
            this.state = 'open';
        }
    }
    getState() {
        return this.state;
    }
    reset() {
        this.failures = 0;
        this.state = 'closed';
    }
}
exports.CircuitBreaker = CircuitBreaker;
/**
 * Retry-specific error messages
 */
class RetryError extends Error {
    attempts;
    failureType;
    originalError;
    constructor(message, attempts, failureType, originalError) {
        super(message);
        this.attempts = attempts;
        this.failureType = failureType;
        this.originalError = originalError;
        this.name = 'RetryError';
    }
    format() {
        const lines = [];
        lines.push(`\n⚠️  Operation failed after ${this.attempts} attempts`);
        lines.push(`   Failure Type: ${this.failureType}`);
        lines.push(`   Original Error: ${this.originalError.message}`);
        if (this.failureType === FailureType.TRANSIENT) {
            lines.push('\n   💡 This appears to be a temporary issue.');
            lines.push('      Try again in a few moments.');
        }
        else if (this.failureType === FailureType.PERMANENT) {
            lines.push('\n   💡 This appears to be a persistent issue.');
            lines.push('      Check your configuration and try again.');
        }
        return lines.join('\n');
    }
}
exports.RetryError = RetryError;
/**
 * Helper functions for common retry scenarios
 */
class RetryHelpers {
    /**
     * Retry file operations
     */
    static async retryFileOperation(operation, description) {
        const result = await RetryManager.execute(operation, {
            maxAttempts: 3,
            initialDelay: 500,
            onRetry: (attempt, error) => {
                console.log(`   Retrying ${description} (attempt ${attempt}): ${error.message}`);
            },
        });
        if (!result.success) {
            throw new RetryError(`Failed to ${description}`, result.attempts, result.failureType || FailureType.UNKNOWN, result.error);
        }
        return result.result;
    }
    /**
     * Retry network operations
     */
    static async retryNetworkOperation(operation, description) {
        const result = await RetryManager.execute(operation, {
            maxAttempts: 5,
            initialDelay: 2000,
            maxDelay: 30000,
            strategy: RetryStrategy.EXPONENTIAL_BACKOFF,
            onRetry: (attempt, error) => {
                console.log(`   Retrying ${description} (attempt ${attempt}): ${error.message}`);
            },
        });
        if (!result.success) {
            throw new RetryError(`Network operation failed: ${description}`, result.attempts, result.failureType || FailureType.UNKNOWN, result.error);
        }
        return result.result;
    }
    /**
     * Retry database operations
     */
    static async retryDatabaseOperation(operation, description) {
        const result = await RetryManager.execute(operation, {
            maxAttempts: 3,
            initialDelay: 1000,
            strategy: RetryStrategy.LINEAR_BACKOFF,
            onRetry: (attempt, error) => {
                console.log(`   Retrying ${description} (attempt ${attempt}): ${error.message}`);
            },
            shouldRetry: (error) => {
                // Don't retry on syntax errors or schema issues
                return !error.message.includes('syntax') &&
                    !error.message.includes('schema');
            },
        });
        if (!result.success) {
            throw new RetryError(`Database operation failed: ${description}`, result.attempts, result.failureType || FailureType.UNKNOWN, result.error);
        }
        return result.result;
    }
}
exports.RetryHelpers = RetryHelpers;
//# sourceMappingURL=retry-manager.js.map