/**
 * Retry Handler Utility
 * Provides retry logic with exponential backoff for transient failures
 */

export class RetryHandler {
  /**
   * Execute a function with retry logic
   * @param {Function} fn - Async function to execute
   * @param {Object} options - Retry options
   * @returns {Promise<any>} Function result
   */
  static async withRetry(fn, options = {}) {
    const {
      maxRetries = 3,
      retryDelay = 1000,
      backoffMultiplier = 2,
      retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED'],
      onRetry = null
    } = options;
    
    let lastError;
    let delay = retryDelay;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error;
        
        // Check if error is retryable
        const isRetryable = retryableErrors.some(code => 
          error.code === code || 
          error.message?.includes(code) ||
          error.message?.includes('timeout') ||
          error.message?.includes('connection')
        );
        
        // Don't retry on last attempt or if error is not retryable
        if (!isRetryable || attempt === maxRetries) {
          if (attempt > 0) {
            console.error(`❌ Retry failed after ${attempt} attempts:`, error.message);
          }
          throw error;
        }
        
        // Call retry callback if provided
        if (onRetry) {
          onRetry(attempt + 1, maxRetries, error, delay);
        } else {
          console.warn(`⚠️ Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms:`, error.message);
        }
        
        // Wait before retry with exponential backoff
        await new Promise(resolve => setTimeout(resolve, delay));
        delay *= backoffMultiplier;
      }
    }
    
    throw lastError;
  }
  
  /**
   * Check if an error is retryable
   * @param {Error} error - Error to check
   * @param {Array<string>} retryableCodes - List of retryable error codes
   * @returns {boolean} True if error is retryable
   */
  static isRetryableError(error, retryableCodes = ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND']) {
    return retryableCodes.some(code => 
      error.code === code || 
      error.message?.includes(code)
    );
  }
}

