// DATE support: ISO "yyyy-mm-dd" parsing and epoch-day conversion
// (days since 1970-01-01, proleptic Gregorian).

///|
pub fn days_from_civil(y : Int, m : Int, d : Int) -> Int {
  let y = if m <= 2 { y - 1 } else { y }
  let era = (if y >= 0 { y } else { y - 399 }) / 400
  let yoe = y - era * 400 // [0, 399]
  let mp = (m + 9) % 12 // March = 0
  let doy = (153 * mp + 2) / 5 + d - 1 // [0, 365]
  let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy // [0, 146096]
  era * 146097 + doe - 719468
}

///|
pub fn civil_from_days(z : Int) -> (Int, Int, Int) {
  let z = z + 719468
  let era = (if z >= 0 { z } else { z - 146096 }) / 146097
  let doe = z - era * 146097 // [0, 146096]
  let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 // [0, 399]
  let y = yoe + era * 400
  let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) // [0, 365]
  let mp = (5 * doy + 2) / 153 // [0, 11]
  let d = doy - (153 * mp + 2) / 5 + 1 // [1, 31]
  let m = if mp < 10 { mp + 3 } else { mp - 9 } // [1, 12]
  (if m <= 2 { y + 1 } else { y }, m, d)
}

///|
fn is_leap(y : Int) -> Bool {
  (y % 4 == 0 && y % 100 != 0) || y % 400 == 0
}

///|
fn days_in_month(y : Int, m : Int) -> Int {
  match m {
    1 => 31
    2 => if is_leap(y) { 29 } else { 28 }
    3 => 31
    4 => 30
    5 => 31
    6 => 30
    7 => 31
    8 => 31
    9 => 30
    10 => 31
    11 => 30
    12 => 31
    _ => 0
  }
}

///|
/// Strict ISO parse: "yyyy-mm-dd" with real calendar validation.
/// Returns epoch days, or None on any malformed input.
pub fn parse_date(s : String) -> Int? {
  let cs : Array[Char] = []
  for ch in s {
    cs.push(ch)
  }
  if cs.length() != 10 || cs[4] != '-' || cs[7] != '-' {
    return None
  }
  let y = match parse_digits4(cs, 0) {
    Some(v) => v
    None => return None
  }
  let m = match parse_digits2(cs, 5) {
    Some(v) => v
    None => return None
  }
  let d = match parse_digits2(cs, 8) {
    Some(v) => v
    None => return None
  }
  if m < 1 || m > 12 {
    return None
  }
  if d < 1 || d > days_in_month(y, m) {
    return None
  }
  Some(days_from_civil(y, m, d))
}

///|
fn digit(ch : Char) -> Int? {
  let v = ch.to_int()
  if v >= 48 && v <= 57 {
    Some(v - 48)
  } else {
    None
  }
}

///|
fn parse_digits4(cs : Array[Char], off : Int) -> Int? {
  match
    (digit(cs[off]), digit(cs[off + 1]), digit(cs[off + 2]), digit(cs[off + 3])) {
    (Some(a), Some(b), Some(c), Some(dd)) =>
      Some(a * 1000 + b * 100 + c * 10 + dd)
    _ => None
  }
}

///|
fn parse_digits2(cs : Array[Char], off : Int) -> Int? {
  match (digit(cs[off]), digit(cs[off + 1])) {
    (Some(a), Some(b)) => Some(a * 10 + b)
    _ => None
  }
}

///|
pub fn epoch_to_string(days : Int) -> String {
  let (y, m, d) = civil_from_days(days)
  let sb = StringBuilder()
  sb.write_string(y.to_string())
  sb.write_char('-')
  if m < 10 {
    sb.write_char('0')
  }
  sb.write_string(m.to_string())
  sb.write_char('-')
  if d < 10 {
    sb.write_char('0')
  }
  sb.write_string(d.to_string())
  sb.to_string()
}

///|
/// Calendar field extraction from epoch days (EXTRACT support).
pub fn epoch_year(days : Int) -> Int {
  let (y, _, _) = civil_from_days(days)
  y
}

///|
pub fn epoch_month(days : Int) -> Int {
  let (_, m, _) = civil_from_days(days)
  m
}

///|
pub fn epoch_day(days : Int) -> Int {
  let (_, _, d) = civil_from_days(days)
  d
}