/**
 * Retry com backoff exponencial para operacoes transientes.
 * Util para mutations do Convex que podem falhar temporariamente.
 */

export type RetryOptions = {
  /** Numero maximo de tentativas (default: 3) */
  maxRetries?: number
  /** Delay base em ms (default: 100) */
  baseDelayMs?: number
  /** Delay maximo em ms (default: 2000) */
  maxDelayMs?: number
  /** Funcao para determinar se erro e retryable (default: true para todos exceto validacao) */
  isRetryable?: (error: unknown) => boolean
}

const DEFAULT_OPTIONS: Required<RetryOptions> = {
  maxRetries: 3,
  baseDelayMs: 100,
  maxDelayMs: 2000,
  isRetryable: (error: unknown) => {
    // Nao retry em erros de validacao
    if (error instanceof Error) {
      const msg = error.message.toLowerCase()
      if (
        msg.includes("invalido") ||
        msg.includes("invalid") ||
        msg.includes("not found") ||
        msg.includes("unauthorized") ||
        msg.includes("forbidden")
      ) {
        return false
      }
    }
    return true
  },
}

/**
 * Executa uma funcao com retry e backoff exponencial.
 *
 * @example
 * ```ts
 * const result = await withRetry(
 *   () => client.mutation(api.liveChat.postMachineMessage, { ... }),
 *   { maxRetries: 3, baseDelayMs: 100 }
 * )
 * ```
 */
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
  const opts = { ...DEFAULT_OPTIONS, ...options }
  let lastError: unknown

  for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error

      // Nao retry se erro nao for retryable
      if (!opts.isRetryable(error)) {
        throw error
      }

      // Ultima tentativa - nao esperar, apenas lancar
      if (attempt >= opts.maxRetries) {
        break
      }

      // Calcular delay com backoff exponencial + jitter
      const exponentialDelay = opts.baseDelayMs * Math.pow(2, attempt)
      const jitter = Math.random() * opts.baseDelayMs
      const delay = Math.min(exponentialDelay + jitter, opts.maxDelayMs)

      await sleep(delay)
    }
  }

  throw lastError
}

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