///|
/// retry_index is zero-based, elapsed_ms must use a monotonic clock.
/// replay_safe opts non-idempotent methods in only when the caller knows that
/// the server implements deduplication or equivalent replay-safe semantics.
pub fn decide(
  settings : Settings,
  http_method~ : String,
  status~ : Int,
  retry_index~ : Int,
  elapsed_ms~ : Int64,
  body_replayable~ : Bool,
  replay_safe? : Bool = false,
  server_delay_ms? : Int64 = 0L,
  sample_ppm? : Int = 1000000,
) -> Decision {
  if !valid_settings(settings) {
    return Stop(InvalidSettings)
  }
  if retry_index < 0 ||
    elapsed_ms < 0L ||
    server_delay_ms < 0L ||
    sample_ppm < 0 ||
    sample_ppm > 1000000 ||
    status < 100 ||
    status > 599 {
    return Stop(InvalidInput)
  }
  if !retryable_status(status) {
    return Stop(NotRetryable)
  }
  if !body_replayable || (!idempotent_method(http_method) && !replay_safe) {
    return Stop(NotReplayable)
  }
  if retry_index >= settings.max_retries {
    return Stop(AttemptsExhausted)
  }
  if elapsed_ms >= settings.total_budget_ms {
    return Stop(BudgetExhausted)
  }
  if server_delay_ms > settings.max_delay_ms {
    return Stop(ServerDelayExceedsCap)
  }
  let ceiling = backoff_ceiling(settings, retry_index).unwrap()
  let client_delay = jitter_delay(ceiling, sample_ppm).unwrap()
  let delay = if server_delay_ms > client_delay {
    server_delay_ms
  } else {
    client_delay
  }
  let remaining = settings.total_budget_ms - elapsed_ms
  // Never clamp a server minimum to fit the remaining budget.
  if settings.request_reserve_ms >= remaining ||
    delay >= remaining - settings.request_reserve_ms {
    return Stop(BudgetExhausted)
  }
  Wait(delay)
}