import 'api_exception.dart';

/// Determines whether and how a failed API request should be retried.
///
/// Implementations can encode strategies such as exponential back-off,
/// fixed-interval retry, or no-retry at all.
abstract class RetryPolicy {
  /// Maximum number of retry attempts (not counting the initial attempt).
  int get maxAttempts;

  /// Returns the [Duration] to wait before attempt number [attempt].
  ///
  /// [attempt] is 1-based: the first retry is attempt 1.
  Duration delayFor(int attempt);

  /// Returns `true` if the [exception] is considered retryable.
  ///
  /// Typically only transient errors (5xx, network timeouts) are retried;
  /// client errors (4xx) are not.
  bool shouldRetry(ApiException exception);
}

/// A [RetryPolicy] that never retries.
class NoRetryPolicy implements RetryPolicy {
  const NoRetryPolicy();

  @override
  int get maxAttempts => 0;

  @override
  Duration delayFor(int attempt) => Duration.zero;

  @override
  bool shouldRetry(ApiException exception) => false;
}

/// A [RetryPolicy] with exponential back-off for transient server errors.
///
/// Retries up to [maxAttempts] times on 5xx responses or network-level errors
/// (status code -1). The delay doubles with each attempt, starting at
/// [initialDelay], capped at [maxDelay].
class ExponentialBackoffRetryPolicy implements RetryPolicy {
  @override
  final int maxAttempts;

  /// Delay before the first retry.
  final Duration initialDelay;

  /// Upper bound on the computed delay.
  final Duration maxDelay;

  const ExponentialBackoffRetryPolicy({
    this.maxAttempts = 3,
    this.initialDelay = const Duration(milliseconds: 500),
    this.maxDelay = const Duration(seconds: 30),
  });

  @override
  Duration delayFor(int attempt) {
    final ms = initialDelay.inMilliseconds * (1 << (attempt - 1));
    return Duration(milliseconds: ms.clamp(0, maxDelay.inMilliseconds));
  }

  @override
  bool shouldRetry(ApiException exception) {
    // Retry on network-level errors or 5xx server errors.
    return exception.statusCode == -1 || exception.statusCode >= 500;
  }
}
