///|
/// Controls whether and how a failed delivery is retried.
///
/// - `None`: no retry, the first failure moves the event to the dead letter queue.
/// - `Fixed(delay_ms)`: every retry waits the same delay.
/// - `Exponential(initial_ms, multiplier, max_ms)`: delay grows by a multiplier,
///   capped at `max_ms`.
pub(all) enum RetryPolicy {
  None
  Fixed(Int)
  Exponential(Int, Double, Int)
} derive(Eq, @debug.Debug)

///|
/// Returns the delay before retry `attempt`, where attempt starts at 1 for the
/// first retry after the initial failure.
pub fn RetryPolicy::next_delay_ms(self : RetryPolicy, attempt : Int) -> Int {
  match self {
    None => 0
    Fixed(delay_ms) =>
      if attempt > 0 {
        if delay_ms < 0 {
          0
        } else {
          delay_ms
        }
      } else {
        0
      }
    Exponential(initial_ms, multiplier, max_ms) =>
      if attempt <= 0 {
        0
      } else {
        let exponent = (attempt - 1).to_double()
        let raw = initial_ms.to_double() * @math.pow(multiplier, exponent)
        let capped = if raw > max_ms.to_double() {
          max_ms
        } else {
          raw.to_int()
        }
        if capped < 0 {
          0
        } else {
          capped
        }
      }
  }
}

///|
/// Validates the policy parameters used by a Hook.
pub fn RetryPolicy::validate(self : RetryPolicy) -> Result[Unit, String] {
  match self {
    None => Ok(())
    Fixed(delay_ms) =>
      if delay_ms < 0 {
        Err("retry delay must be non-negative")
      } else {
        Ok(())
      }
    Exponential(initial_ms, multiplier, max_ms) =>
      if initial_ms <= 0 {
        Err("initial retry delay must be positive")
      } else if multiplier < 1.0 {
        Err("retry multiplier must be at least 1")
      } else if max_ms < initial_ms {
        Err("max retry delay must be at least the initial delay")
      } else {
        Ok(())
      }
  }
}

///|
/// Returns true when another attempt is allowed.
pub fn RetryPolicy::should_retry(
  self : RetryPolicy,
  attempt : Int,
  max_attempts : Int,
) -> Bool {
  match self {
    None => false
    _ => attempt < max_attempts
  }
}

///|
/// A short human-readable description used by the CLI and audit logs.
pub fn RetryPolicy::describe(self : RetryPolicy) -> String {
  match self {
    None => "none"
    Fixed(delay_ms) => "fixed(\{delay_ms}ms)"
    Exponential(initial_ms, multiplier, max_ms) =>
      "exponential(\{initial_ms}ms x\{multiplier}, max \{max_ms}ms)"
  }
}