///|
/// Checks if a year is a leap year according to Gregorian calendar rules.
fn is_leap_year(year : Int) -> Bool {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

///|
/// Returns the number of days in the given month of the specified year.
fn days_in_month(year : Int, month : Int) -> Int {
  match month {
    1 => 31
    2 => if is_leap_year(year) { 29 } else { 28 }
    3 => 31
    4 => 30
    5 => 31
    6 => 30
    7 => 31
    8 => 31
    9 => 30
    10 => 31
    11 => 30
    12 => 31
    _ => 0
  }
}

///|
/// Maps a 3-character day name to weekday index (0 = Monday .. 6 = Sunday).
fn parse_weekday(c0 : Char, c1 : Char, c2 : Char) -> Int64? {
  match (c0, c1, c2) {
    ('M', 'o', 'n') => Some(0L)
    ('T', 'u', 'e') => Some(1L)
    ('W', 'e', 'd') => Some(2L)
    ('T', 'h', 'u') => Some(3L)
    ('F', 'r', 'i') => Some(4L)
    ('S', 'a', 't') => Some(5L)
    ('S', 'u', 'n') => Some(6L)
    _ => None
  }
}

///|
/// Maps a 3-character month name to 1-based month index (1..12).
fn parse_month(c0 : Char, c1 : Char, c2 : Char) -> Int? {
  match (c0, c1, c2) {
    ('J', 'a', 'n') => Some(1)
    ('F', 'e', 'b') => Some(2)
    ('M', 'a', 'r') => Some(3)
    ('A', 'p', 'r') => Some(4)
    ('M', 'a', 'y') => Some(5)
    ('J', 'u', 'n') => Some(6)
    ('J', 'u', 'l') => Some(7)
    ('A', 'u', 'g') => Some(8)
    ('S', 'e', 'p') => Some(9)
    ('O', 'c', 't') => Some(10)
    ('N', 'o', 'v') => Some(11)
    ('D', 'e', 'c') => Some(12)
    _ => None
  }
}

///|
/// Parses a single ASCII decimal digit.
fn parse_digit(c : Char) -> Int? {
  match c {
    '0' => Some(0)
    '1' => Some(1)
    '2' => Some(2)
    '3' => Some(3)
    '4' => Some(4)
    '5' => Some(5)
    '6' => Some(6)
    '7' => Some(7)
    '8' => Some(8)
    '9' => Some(9)
    _ => None
  }
}

///|
/// Parses two ASCII decimal digits into an Int (0..99).
fn parse_2digit(c0 : Char, c1 : Char) -> Int? {
  match (parse_digit(c0), parse_digit(c1)) {
    (Some(d0), Some(d1)) => Some(d0 * 10 + d1)
    _ => None
  }
}

///|
/// Parses four ASCII decimal digits into an Int (0..9999).
fn parse_4digit(c0 : Char, c1 : Char, c2 : Char, c3 : Char) -> Int? {
  match (parse_digit(c0), parse_digit(c1), parse_digit(c2), parse_digit(c3)) {
    (Some(d0), Some(d1), Some(d2), Some(d3)) =>
      Some(d0 * 1000 + d1 * 100 + d2 * 10 + d3)
    _ => None
  }
}

///|
/// Computes days relative to 1970 using complete Gregorian years and months.
fn days_from_civil(year : Int, month : Int, day : Int) -> Int64 {
  let previous = (year - 1).to_int64()
  let mut days = previous * 365L +
    previous / 4L -
    previous / 100L +
    previous / 400L
  for m = 1; m < month; m = m + 1 {
    days = days + days_in_month(year, m).to_int64()
  }
  days + day.to_int64() - 1L - 719162L
}

///|
/// Parses a strict IMF-fixdate HTTP-date string (RFC 9110 Section 5.6.7 / RFC 7231 Section 7.1.1.1)
/// into a Unix timestamp (seconds since 1970-01-01T00:00:00Z).
///
/// Expected exact format: `Sun, 06 Nov 1994 08:49:37 GMT`
/// - Length must be exactly 29 characters.
/// - ASCII case sensitive.
/// - Exact punctuation (comma, spaces, colons, "GMT").
///
/// Validation rules:
/// - Gregorian calendar years in range 1601..9999.
/// - Month and day valid according to Gregorian calendar and leap years.
/// - Time fields: hour 00..23, minute 00..59, second 00..59.
/// - Weekday must match the calculated Gregorian day of week.
///
/// Limitations:
/// - Pure computation with zero IO, system clock access, or external dependencies.
/// - Obsolete formats (RFC 850 / RFC 1036 and ANSI C asctime) are not supported.
/// - Leap seconds (second 60) are deliberately rejected by this strict parser.
/// - Years before 1601 or after 9999 are rejected.
pub fn parse_http_date(value : String) -> Int64? {
  if value.length() != 29 {
    return None
  }
  let chars = value.to_array()
  if chars.length() != 29 {
    return None
  }
  if chars[3] != ',' ||
    chars[4] != ' ' ||
    chars[7] != ' ' ||
    chars[11] != ' ' ||
    chars[16] != ' ' ||
    chars[19] != ':' ||
    chars[22] != ':' ||
    chars[25] != ' ' ||
    chars[26] != 'G' ||
    chars[27] != 'M' ||
    chars[28] != 'T' {
    return None
  }

  let w_opt = parse_weekday(chars[0], chars[1], chars[2])
  let d_opt = parse_2digit(chars[5], chars[6])
  let m_opt = parse_month(chars[8], chars[9], chars[10])
  let y_opt = parse_4digit(chars[12], chars[13], chars[14], chars[15])
  let h_opt = parse_2digit(chars[17], chars[18])
  let min_opt = parse_2digit(chars[20], chars[21])
  let s_opt = parse_2digit(chars[23], chars[24])

  match (w_opt, d_opt, m_opt, y_opt, h_opt, min_opt, s_opt) {
    (
      Some(expected_weekday),
      Some(day),
      Some(month),
      Some(year),
      Some(hour),
      Some(minute),
      Some(second),
    ) =>
      if year < 1601 || year > 9999 {
        None
      } else if day < 1 || day > days_in_month(year, month) {
        None
      } else if hour > 23 || minute > 59 || second > 59 {
        None
      } else {
        let days = days_from_civil(year, month, day)
        let w = (days + 3L) % 7L
        let actual_weekday = if w < 0L { w + 7L } else { w }
        if actual_weekday != expected_weekday {
          None
        } else {
          let total_seconds = days * 86400L +
            hour.to_int64() * 3600L +
            minute.to_int64() * 60L +
            second.to_int64()
          Some(total_seconds)
        }
      }
    _ => None
  }
}