"use strict";

/**
 * Async utilities for common asynchronous patterns and error handling
 */

import { logger } from './loggerUtils';

/**
 * Wrapper for async operations with automatic error handling
 * Returns null on error instead of throwing
 */
export async function withErrorHandling(operation, errorHandler, context) {
  try {
    return await operation();
  } catch (error) {
    if (errorHandler) {
      errorHandler(error);
    } else {
      logger.error(`Error in ${context || 'operation'}`, error instanceof Error ? error : new Error(String(error)), {
        component: 'asyncUtils',
        method: 'withErrorHandling'
      });
    }
    return null;
  }
}

/**
 * Execute multiple async operations in parallel with error handling
 */
export async function parallelWithErrorHandling(operations, errorHandler) {
  const results = await Promise.allSettled(operations.map((op, index) => withErrorHandling(op, error => errorHandler?.(error, index))));
  return results.map(result => result.status === 'fulfilled' ? result.value : null);
}

/**
 * Retry an async operation with exponential backoff
 * 
 * By default, does not retry on 4xx errors (client errors).
 * Use shouldRetry callback to customize retry behavior.
 */
export async function retryAsync(operation, maxRetries = 3, baseDelay = 1000, shouldRetry) {
  let lastError;

  // Default shouldRetry: don't retry on 4xx errors
  const defaultShouldRetry = error => {
    // Don't retry on 4xx errors (client errors)
    if (error?.response?.status >= 400 && error?.response?.status < 500) {
      return false;
    }
    return true;
  };
  const retryCheck = shouldRetry || defaultShouldRetry;
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      if (attempt === maxRetries) {
        break;
      }
      if (!retryCheck(error)) {
        break;
      }

      // Calculate delay with exponential backoff and jitter
      const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw lastError;
}

/**
 * Debounce async function calls
 */
export function debounceAsync(func, delay) {
  let timeoutId;
  const lastPromise = null;
  return (...args) => {
    return new Promise((resolve, reject) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(async () => {
        try {
          const result = await func(...args);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      }, delay);
    });
  };
}

/**
 * Throttle async function calls
 */
export function throttleAsync(func, limit, interval) {
  let inThrottle = false;
  let lastPromise = null;
  return (...args) => {
    if (inThrottle) {
      return lastPromise;
    }
    inThrottle = true;
    lastPromise = func(...args);
    setTimeout(() => {
      inThrottle = false;
    }, interval);
    return lastPromise;
  };
}

/**
 * Execute async operations sequentially with progress tracking
 */
export async function sequentialWithProgress(operations, onProgress) {
  const results = [];
  for (let i = 0; i < operations.length; i++) {
    const result = await operations[i]();
    results.push(result);
    onProgress?.(i + 1, operations.length);
  }
  return results;
}

/**
 * Batch async operations
 */
export async function batchAsync(items, batchSize, processor) {
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    await processor(batch);
  }
}

/**
 * Create a cancellable async operation
 */
export function createCancellableAsync(operation) {
  let abortController = null;
  return {
    execute: async () => {
      abortController = new AbortController();
      return await operation(abortController.signal);
    },
    cancel: () => {
      abortController?.abort();
    }
  };
}

/**
 * Timeout wrapper for async operations
 */
export async function withTimeout(operation, timeoutMs, timeoutMessage) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(timeoutMessage || `Operation timed out after ${timeoutMs}ms`));
    }, timeoutMs);
  });
  return Promise.race([operation, timeoutPromise]);
}

/**
 * Execute async operation with loading state
 */
export async function withLoadingState(operation, setLoading) {
  setLoading(true);
  try {
    return await operation();
  } finally {
    setLoading(false);
  }
}

/**
 * Create a promise that resolves after a delay
 */
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

/**
 * Execute async operation with retry on specific errors
 */
export async function retryOnError(operation, retryableErrors, maxRetries = 3) {
  return retryAsync(operation, maxRetries, 1000, error => {
    const errorCode = error?.code || error?.status || error?.message;
    return retryableErrors.includes(errorCode);
  });
}
//# sourceMappingURL=asyncUtils.js.map