import { logger } from "@/core/logger";

interface RetryOptions {
  /** Maximum number of retry attempts (default: 2) */
  maxRetries?: number;
  /** Base delay in ms between retries (default: 1000) */
  baseDelay?: number;
}

/**
 * Retry a function on transient errors (network failures, 5xx responses).
 * Does NOT retry on 4xx errors (validation, auth, etc.) since those won't succeed on retry.
 */
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
  const { maxRetries = 2, baseDelay = 1000 } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      // eslint-disable-next-line no-await-in-loop -- Sequential retries are intentional
      return await fn();
    } catch (error) {
      if (attempt === maxRetries || !isRetryable(error)) {
        throw error;
      }

      const delay = baseDelay * Math.pow(2, attempt);
      logger.warn(`Retrying after transient error (attempt ${attempt + 1}/${maxRetries})`, {
        error: error instanceof Error ? error.message : String(error),
        delay,
      });

      // eslint-disable-next-line no-await-in-loop -- Sequential delay between retries
      await sleep(delay);
    }
  }

  // Unreachable, but TypeScript needs it
  throw new Error("Retry exhausted");
}

function isRetryable(error: unknown): boolean {
  // Network errors (fetch failures, timeouts)
  if (error instanceof TypeError && error.message.includes("fetch")) {
    return true;
  }

  // Check for status code on API errors
  if (error && typeof error === "object" && "status" in error) {
    const status = (error as { status: number }).status;
    // Retry on 5xx server errors, 408 timeout, 429 too many requests
    return status >= 500 || status === 408 || status === 429;
  }

  // Network-related error messages
  if (error instanceof Error) {
    const msg = error.message.toLowerCase();
    return msg.includes("network") || msg.includes("timeout") || msg.includes("econnrefused");
  }

  return false;
}

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