export class RetryHelper {
  static async retry<T>(
    fn: () => Promise<T>,
    options: {
      maxAttempts?: number;
      delay?: number;
      backoff?: boolean;
      onRetry?: (attempt: number, error: Error) => void;
    } = {}
  ): Promise<T> {
    const {
      maxAttempts = 3,
      delay = 1000,
      backoff = true,
      onRetry
    } = options;

    let lastError: Error | undefined;
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error as Error;
        
        if (attempt === maxAttempts) {
          throw lastError;
        }
        
        if (onRetry) {
          onRetry(attempt, lastError);
        }
        
        const currentDelay = backoff ? delay * attempt : delay;
        await this.sleep(currentDelay);
      }
    }
    
    throw lastError!;
  }

  static async retryWithCondition<T>(
    fn: () => Promise<T>,
    condition: (result: T) => boolean,
    options: {
      maxAttempts?: number;
      delay?: number;
      timeout?: number;
      onRetry?: (attempt: number, lastResult: T) => void;
    } = {}
  ): Promise<T> {
    const {
      maxAttempts = 10,
      delay = 500,
      timeout = 10000,
      onRetry
    } = options;

    const startTime = Date.now();
    let lastResult: T | undefined;
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        lastResult = await fn();
        
        if (condition(lastResult)) {
          return lastResult;
        }
        
        if (Date.now() - startTime > timeout) {
          throw new Error(`Timeout after ${timeout}ms waiting for condition to be met`);
        }
        
        if (onRetry && lastResult !== undefined) {
          onRetry(attempt, lastResult);
        }
        
        await this.sleep(delay);
      } catch (error) {
        if (Date.now() - startTime > timeout) {
          throw new Error(`Timeout after ${timeout}ms: ${error}`);
        }
        
        await this.sleep(delay);
      }
    }
    
    throw new Error(`Condition not met after ${maxAttempts} attempts`);
  }

  static async retryElementAction<T>(
    fn: () => Promise<T>,
    options: {
      maxAttempts?: number;
      delay?: number;
      ignoreErrors?: string[];
    } = {}
  ): Promise<T> {
    const {
      maxAttempts = 3,
      delay = 1000,
      ignoreErrors = ['Timeout', 'Element not found', 'Element not visible']
    } = options;

    return this.retry(fn, {
      maxAttempts,
      delay,
      backoff: true,
      onRetry: (attempt, error) => {
        const shouldIgnore = ignoreErrors.some(ignoredError => 
          error.message.includes(ignoredError)
        );
        
        if (shouldIgnore) {
          console.log(`Attempt ${attempt} failed with ignorable error: ${error.message}`);
        }
      }
    });
  }

  static async retryNetworkRequest<T>(
    fn: () => Promise<T>,
    options: {
      maxAttempts?: number;
      delay?: number;
      retryableStatuses?: number[];
    } = {}
  ): Promise<T> {
    const {
      maxAttempts = 3,
      delay = 2000,
      retryableStatuses = [408, 429, 500, 502, 503, 504]
    } = options;

    return this.retry(fn, {
      maxAttempts,
      delay,
      backoff: true,
      onRetry: (attempt, error) => {
        console.log(`Network request attempt ${attempt} failed: ${error.message}`);
      }
    });
  }

  static async retryClick(
    clickFn: () => Promise<void>,
    options: {
      maxAttempts?: number;
      delay?: number;
    } = {}
  ): Promise<void> {
    const { maxAttempts = 3, delay = 500 } = options;

    return this.retry(clickFn, {
      maxAttempts,
      delay,
      backoff: false,
      onRetry: (attempt, error) => {
        console.log(`Click attempt ${attempt} failed: ${error.message}`);
      }
    });
  }

  static async retryFill(
    fillFn: () => Promise<void>,
    options: {
      maxAttempts?: number;
      delay?: number;
    } = {}
  ): Promise<void> {
    const { maxAttempts = 3, delay = 500 } = options;

    return this.retry(fillFn, {
      maxAttempts,
      delay,
      backoff: false,
      onRetry: (attempt, error) => {
        console.log(`Fill attempt ${attempt} failed: ${error.message}`);
      }
    });
  }

  static async retryNavigation(
    navigateFn: () => Promise<void>,
    options: {
      maxAttempts?: number;
      delay?: number;
    } = {}
  ): Promise<void> {
    const { maxAttempts = 3, delay = 1000 } = options;

    return this.retry(navigateFn, {
      maxAttempts,
      delay,
      backoff: true,
      onRetry: (attempt, error) => {
        console.log(`Navigation attempt ${attempt} failed: ${error.message}`);
      }
    });
  }

  static async retryAssertion<T>(
    assertionFn: () => Promise<T>,
    options: {
      maxAttempts?: number;
      delay?: number;
    } = {}
  ): Promise<T> {
    const { maxAttempts = 5, delay = 500 } = options;

    return this.retry(assertionFn, {
      maxAttempts,
      delay,
      backoff: false,
      onRetry: (attempt, error) => {
        console.log(`Assertion attempt ${attempt} failed: ${error.message}`);
      }
    });
  }

  private static sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  static createRetryPolicy<T>(
    fn: () => Promise<T>,
    policy: {
      maxAttempts: number;
      initialDelay: number;
      maxDelay?: number;
      backoffMultiplier?: number;
      retryCondition?: (error: Error) => boolean;
    }
  ): () => Promise<T> {
    const {
      maxAttempts,
      initialDelay,
      maxDelay = 30000,
      backoffMultiplier = 2,
      retryCondition
    } = policy;

    return async () => {
      let currentDelay = initialDelay;
      let lastError: Error | undefined;

      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await fn();
        } catch (error) {
          lastError = error as Error;

          if (retryCondition && !retryCondition(lastError)) {
            throw lastError;
          }

          if (attempt === maxAttempts) {
            throw lastError;
          }

          console.log(`Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`);
          await this.sleep(currentDelay);
          currentDelay = Math.min(currentDelay * backoffMultiplier, maxDelay);
        }
      }

      throw lastError!;
    };
  }

  static async retryWithTimeout<T>(
    fn: () => Promise<T>,
    timeout: number,
    options: {
      maxAttempts?: number;
      delay?: number;
    } = {}
  ): Promise<T> {
    const { maxAttempts = 3, delay = 1000 } = options;

    return Promise.race([
      this.retry(fn, { maxAttempts, delay }),
      new Promise<T>((_, reject) =>
        setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout)
      )
    ]);
  }
}
