///|
pub(all) enum NativeOffset {
  KnownOffsetMinutes(Int32)
  UnknownOffset
} derive(Eq, Debug)

///|
/// A native timestamp's precision and offset, independent of any format library.
pub struct NativeTimestamp {
  year : Int32
  month : Int32?
  day : Int32?
  hour : Int32?
  minute : Int32?
  second : Int32?
  fraction : @smithy.NodeNumber?
  offset : NativeOffset
} derive(Debug)

///|
fn calendar_day(year : Int32, month : Int32, day : Int32) -> Bool {
  if year < 1 || year > 9999 || month < 1 || month > 12 || day < 1 {
    return false
  }
  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
  }
  day <= days
}

///|
pub fn NativeTimestamp::new(
  year : Int32,
  month~ : Int32? = None,
  day~ : Int32? = None,
  hour~ : Int32? = None,
  minute~ : Int32? = None,
  second~ : Int32? = None,
  fraction~ : @smithy.NodeNumber? = None,
  offset~ : NativeOffset = UnknownOffset,
) -> NativeTimestamp raise ConfigurationError {
  let bad = fn() {
    ConfigurationError(
      "I expected consistent timestamp precision, a valid calendar date and a valid offset.",
    )
  }
  guard year >= 1 && year <= 9999 else { raise bad() }
  if month is Some(m) && (m < 1 || m > 12) {
    raise bad()
  }
  if day is Some(d) {
    guard month is Some(m) && calendar_day(year, m, d) else { raise bad() }
  }
  if hour is Some(h) {
    guard day is Some(_) && h >= 0 && h < 24 && minute is Some(_) else {
      raise bad()
    }
  }
  if minute is Some(m) {
    guard hour is Some(_) && m >= 0 && m < 60 else { raise bad() }
  }
  if second is Some(s) {
    guard minute is Some(_) && s >= 0 && s < 60 else { raise bad() }
  }
  if fraction is Some(f) {
    guard second is Some(_) &&
      f.numeric_compare(@smithy.NodeNumber::parse("0").unwrap()) >= 0 &&
      f.numeric_compare(@smithy.NodeNumber::parse("1").unwrap()) < 0 else {
      raise bad()
    }
  }
  if offset is KnownOffsetMinutes(m) &&
    (minute is None || m <= -1440 || m >= 1440) {
    raise bad()
  }
  { year, month, day, hour, minute, second, fraction, offset, }
}

///|
pub fn NativeTimestamp::year(self : NativeTimestamp) -> Int32 {
  self.year
}

///|
pub fn NativeTimestamp::month(self : NativeTimestamp) -> Int32? {
  self.month
}

///|
pub fn NativeTimestamp::day(self : NativeTimestamp) -> Int32? {
  self.day
}

///|
pub fn NativeTimestamp::hour(self : NativeTimestamp) -> Int32? {
  self.hour
}

///|
pub fn NativeTimestamp::minute(self : NativeTimestamp) -> Int32? {
  self.minute
}

///|
pub fn NativeTimestamp::second(self : NativeTimestamp) -> Int32? {
  self.second
}

///|
pub fn NativeTimestamp::fraction(self : NativeTimestamp) -> @smithy.NodeNumber? {
  self.fraction
}

///|
pub fn NativeTimestamp::offset(self : NativeTimestamp) -> NativeOffset {
  self.offset
}