///|
/// Parameters for pure exponential backoff with symmetric jitter.
pub(all) struct Backoff {
  base_ms : Int
  max_ms : Int
  factor : Double
  jitter : Double
} derive(Eq, Debug)

///|
/// Compares backoff parameters.
pub extend Backoff with Eq::{equal, not_equal}

///|
/// Formats backoff parameters for debugging.
pub extend Backoff with @debug.Debug::{to_repr}

///|
/// Uses 500ms base, 30000ms cap, factor 2, and jitter 0.2.
pub fn Backoff::default() -> Backoff {
  { base_ms: 500, max_ms: 30000, factor: 2.0, jitter: 0.2, }
}

///|
/// Computes a capped delay, truncating fractional milliseconds.
/// Negative attempts behave as zero; random is clamped into [0, 1).
///
/// ```mbt check
/// test {
///   assert_eq(@runtime.Backoff::default().delay_ms(2, 0.5), 2000)
/// }
/// ```
pub fn Backoff::delay_ms(self : Backoff, attempt : Int, random : Double) -> Int {
  let cap = self.max_ms.max(0).to_double()
  let base = self.base_ms.max(0).to_double()
  if base == 0.0 || cap == 0.0 {
    return 0
  }
  let factor = if self.factor >= 0.0 { self.factor } else { 0.0 }
  let raw = base * @math.pow(factor, attempt.max(0).to_double())
  let raw = if raw >= cap { cap } else if raw >= 0.0 { raw } else { 0.0 }
  let random = if !(random >= 0.0) {
    0.0
  } else {
    random.min(0.9999999999999999)
  }
  let jitter = if self.jitter >= 0.0 { self.jitter.min(1.0) } else { 0.0 }
  let result = raw * (1.0 - jitter + 2.0 * jitter * random)
  if result >= cap {
    cap.to_int()
  } else if result > 0.0 {
    result.to_int()
  } else {
    0
  }
}