///|
/// Parses nonnegative seconds (including decimals) or an IMF-fixdate with a clock.
/// Results are truncated to milliseconds and saturated at Int max.
///
/// ```mbt check
/// test {
///   assert_eq(@runtime.parse_retry_after("1.2349"), Some(1234))
/// }
/// ```
pub fn parse_retry_after(value : String, now_unix_ms? : Int64) -> Int? {
  let value = value.trim().to_owned()
  if decimal_ms(value, 1000) is Some(ms) {
    return Some(ms)
  }
  guard now_unix_ms is Some(now) else { return None }
  guard http_date_ms(value) is Some(date) else { return None }
  if date <= now {
    return Some(0)
  }
  // Compare before subtracting: even an Int64-min clock must not overflow.
  if now < date - 2147483647L {
    Some(2147483647)
  } else {
    Some((date - now).to_int())
  }
}

///|
/// Prefers the first retry-after-ms value; falls back to retry-after when that
/// header is absent or unparseable, as the official OpenAI SDKs do.
pub fn retry_after_ms(headers : @http.Headers, now_unix_ms? : Int64) -> Int? {
  if headers.get("retry-after-ms") is Some(value) &&
    decimal_ms(value.trim().to_owned(), 1) is Some(ms) {
    return Some(ms)
  }
  match headers.get("retry-after") {
    Some(value) => parse_retry_after(value, now_unix_ms?)
    None => None
  }
}

///|
// Validate the whole input even after saturation. No floating-point rounding.
fn decimal_ms(value : String, scale : Int) -> Int? {
  let mut whole = 0L
  let mut fraction = 0
  let mut weight = scale
  let mut dot = false
  let mut before = 0
  let mut after = 0
  for c in value {
    if c == '.' && !dot {
      dot = true
    } else if c >= '0' && c <= '9' {
      let digit = c.to_int() - 48
      if dot {
        after += 1
        weight /= 10
        fraction += digit * weight
      } else {
        before += 1
        whole = (whole * 10 + digit.to_int64()).min(2147483647L)
      }
    } else {
      return None
    }
  }
  if before == 0 || (dot && after == 0) {
    return None
  }
  Some(
    (whole * scale.to_int64() + fraction.to_int64()).min(2147483647L).to_int(),
  )
}

///|
fn date_digits(s : String, start : Int, count : Int) -> Int? {
  let mut n = 0
  for i = start; i < start + count; i = i + 1 {
    guard s[i] >= '0' && s[i] <= '9' else { return None }
    n = n * 10 + s[i].to_int() - 48
  }
  Some(n)
}

///|
fn http_date_ms(s : String) -> Int64? {
  guard s.length() == 29 else { return None }
  guard s[3] == ',' &&
    s[4] == ' ' &&
    s[7] == ' ' &&
    s[11] == ' ' &&
    s[16] == ' ' &&
    s[19] == ':' &&
    s[22] == ':' &&
    s[25] == ' ' else {
    return None
  }
  let weekday = match s {
    ['S', 'u', 'n', ..] => 0
    ['M', 'o', 'n', ..] => 1
    ['T', 'u', 'e', ..] => 2
    ['W', 'e', 'd', ..] => 3
    ['T', 'h', 'u', ..] => 4
    ['F', 'r', 'i', ..] => 5
    ['S', 'a', 't', ..] => 6
    _ => return None
  }
  guard s.has_suffix(" GMT") else { return None }
  let month = match (s[8], s[9], s[10]) {
    ('J', 'a', 'n') => 1
    ('F', 'e', 'b') => 2
    ('M', 'a', 'r') => 3
    ('A', 'p', 'r') => 4
    ('M', 'a', 'y') => 5
    ('J', 'u', 'n') => 6
    ('J', 'u', 'l') => 7
    ('A', 'u', 'g') => 8
    ('S', 'e', 'p') => 9
    ('O', 'c', 't') => 10
    ('N', 'o', 'v') => 11
    ('D', 'e', 'c') => 12
    _ => return None
  }
  guard date_digits(s, 5, 2) is Some(day) else { return None }
  guard date_digits(s, 12, 4) is Some(year) else { return None }
  guard date_digits(s, 17, 2) is Some(hour) else { return None }
  guard date_digits(s, 20, 2) is Some(minute) else { return None }
  guard date_digits(s, 23, 2) is Some(second) else { return None }
  let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
  let days = match month {
    2 => if leap { 29 } else { 28 }
    4 | 6 | 9 | 11 => 30
    _ => 31
  }
  guard year >= 1 &&
    day >= 1 &&
    day <= days &&
    hour < 24 &&
    minute < 60 &&
    second <= 60 else {
    return None
  }
  let days = days_from_civil(year, month, day)
  guard ((days + 4) % 7 + 7) % 7 == weekday else { return None }
  Some(
    ((days.to_int64() * 24 + hour.to_int64()) * 60 + minute.to_int64()) * 60000 +
    second.to_int64() * 1000,
  )
}

///|
// March-based Gregorian eras, with 1970-01-01 as day zero.
fn days_from_civil(year : Int, month : Int, day : Int) -> Int {
  let y = year - (if month <= 2 { 1 } else { 0 })
  let era = y / 400
  let yoe = y - era * 400
  let m = month + (if month > 2 { -3 } else { 9 })
  let doy = (153 * m + 2) / 5 + day - 1
  era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy - 719468
}