///|
/// `xsd:dateTime` 校验。
///
/// GPX 里 `metadata/time`、`wpt/time` 都是 `xsd:dateTime`。
/// 这个类型有很多细节容易被忽略:年份至少 4 位、月日时分的范围、
/// 小数秒、时区(`Z` 或 `±hh:mm`)——**时区可以完全省略**,但一旦出现必须合法。

///|
fn read_digits_at(codes : Array[Int], pos : Int, count : Int) -> Int? {
  if pos + count > codes.length() {
    return None
  }
  let mut v = 0
  let mut i = 0
  while i < count {
    let c = codes[pos + i]
    if !is_digit_code(c) {
      return None
    }
    v = v * 10 + (c - 0x30)
    i = i + 1
  }
  Some(v)
}

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

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

///|
/// 校验 `xsd:dateTime` 字面量。
///
/// 词法形式:
///
/// ```text
/// -? YYYY(YYYY*) '-' MM '-' DD 'T' hh ':' mm ':' ss ('.' s+)? ( 'Z' | ('+'|'-') hh ':' mm )?
/// ```
///
/// 注意:
/// - 年份**至少 4 位**,可以是负数(公元前)
/// - 分隔符 `T` 是**大写字母**,不接受空格
/// - 时区**可以省略**;省略时表示"无时区"
/// - 小数秒至少一位,位数不限
/// - 闰秒允许 `ss = 60`
pub fn is_valid_datetime(text : String) -> Bool {
  let codes = codes_of(text)
  let n = codes.length()
  let mut p = 0
  // 可选的负号(公元前)
  if p < n && codes[p] == 0x2D {
    p = p + 1
  }
  // 年份:至少 4 位数字
  let year_start = p
  while p < n && is_digit_code(codes[p]) {
    p = p + 1
  }
  let year_digits = p - year_start
  if year_digits < 4 {
    return false
  }
  let year = read_digits_at(codes, year_start, year_digits)
  let year = match year {
    Some(v) => v
    None => return false
  }
  // '-'
  if p >= n || codes[p] != 0x2D {
    return false
  }
  p = p + 1
  // 月
  let month = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return false
  }
  p = p + 2
  if month < 1 || month > 12 {
    return false
  }
  if p >= n || codes[p] != 0x2D {
    return false
  }
  p = p + 1
  // 日
  let day = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return false
  }
  p = p + 2
  if day < 1 || day > days_in_month(year, month) {
    return false
  }
  // 'T'
  if p >= n || codes[p] != 0x54 {
    return false
  }
  p = p + 1
  // 时
  let hour = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return false
  }
  p = p + 2
  if hour > 23 {
    return false
  }
  if p >= n || codes[p] != 0x3A {
    return false
  }
  p = p + 1
  // 分
  let minute = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return false
  }
  p = p + 2
  if minute > 59 {
    return false
  }
  if p >= n || codes[p] != 0x3A {
    return false
  }
  p = p + 1
  // 秒(允许闰秒 60)
  let second = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return false
  }
  p = p + 2
  if second > 60 {
    return false
  }
  // 可选小数秒
  if p < n && codes[p] == 0x2E {
    p = p + 1
    let frac_start = p
    while p < n && is_digit_code(codes[p]) {
      p = p + 1
    }
    if p == frac_start {
      // 有小数点却没有任何数字
      return false
    }
  }
  // 可选时区
  if p < n {
    let c = codes[p]
    if c == 0x5A {
      // 'Z'
      p = p + 1
    } else if c == 0x2B || c == 0x2D {
      p = p + 1
      let tz_hour = match read_digits_at(codes, p, 2) {
        Some(v) => v
        None => return false
      }
      p = p + 2
      if p >= n || codes[p] != 0x3A {
        return false
      }
      p = p + 1
      let tz_min = match read_digits_at(codes, p, 2) {
        Some(v) => v
        None => return false
      }
      p = p + 2
      // XSD 的时区范围是 -14:00 ~ +14:00
      if tz_hour > 14 || tz_min > 59 {
        return false
      }
      if tz_hour == 14 && tz_min != 0 {
        return false
      }
    } else {
      return false
    }
  }
  // 必须恰好消费完
  p == n
}

// ─────────────────── 可比较的时间表示 ───────────────────

///|
priv struct DtParts {
  year : Int
  month : Int
  day : Int
  hour : Int
  minute : Int
  second : Int
  /// 小数秒(秒的小数部分)
  frac : Double
  /// 时区偏移(分钟,东为正)。`None` 表示**该字面量没有时区**。
  tz : Int?
}

///|
/// 把 `xsd:dateTime` 拆成各部分。与 `is_valid_datetime` 词法一致,
/// 只是顺手把数值取出来而不做校验——调用方应先 `is_valid_datetime`。
fn parse_dt_parts(text : String) -> DtParts? {
  let codes = codes_of(text)
  let n = codes.length()
  let mut p = 0
  // 可选的负号(公元前)
  let mut negative = false
  if p < n && codes[p] == 0x2D {
    negative = true
    p = p + 1
  }
  let year_start = p
  while p < n && is_digit_code(codes[p]) {
    p = p + 1
  }
  let year_digits = p - year_start
  if year_digits < 4 {
    return None
  }
  let year_mag = match read_digits_at(codes, year_start, year_digits) {
    Some(v) => v
    None => return None
  }
  let year = if negative { -year_mag } else { year_mag }
  if p >= n || codes[p] != 0x2D {
    return None
  }
  p = p + 1
  let month = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return None
  }
  p = p + 2
  if p >= n || codes[p] != 0x2D {
    return None
  }
  p = p + 1
  let day = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return None
  }
  p = p + 2
  if p >= n || codes[p] != 0x54 {
    return None
  }
  p = p + 1
  let hour = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return None
  }
  p = p + 2
  if p >= n || codes[p] != 0x3A {
    return None
  }
  p = p + 1
  let minute = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return None
  }
  p = p + 2
  if p >= n || codes[p] != 0x3A {
    return None
  }
  p = p + 1
  let second = match read_digits_at(codes, p, 2) {
    Some(v) => v
    None => return None
  }
  p = p + 2
  // 小数秒:取前 9 位已远超 Double 的实用精度
  let mut frac = 0.0
  if p < n && codes[p] == 0x2E {
    p = p + 1
    let frac_start = p
    while p < n && is_digit_code(codes[p]) {
      p = p + 1
    }
    let mut scale = 0.1
    let mut i = frac_start
    while i < p && i < frac_start + 9 {
      frac = frac + (codes[i] - 0x30).to_double() * scale
      scale = scale * 0.1
      i = i + 1
    }
  }
  // 时区
  let mut tz : Int? = None
  if p < n {
    let c = codes[p]
    if c == 0x5A {
      tz = Some(0)
      p = p + 1
    } else if c == 0x2B || c == 0x2D {
      let neg = c == 0x2D
      p = p + 1
      let tz_hour = match read_digits_at(codes, p, 2) {
        Some(v) => v
        None => return None
      }
      p = p + 2
      if p >= n || codes[p] != 0x3A {
        return None
      }
      p = p + 1
      let tz_min = match read_digits_at(codes, p, 2) {
        Some(v) => v
        None => return None
      }
      p = p + 2
      let total = tz_hour * 60 + tz_min
      tz = Some(if neg { -total } else { total })
    } else {
      return None
    }
  }
  if p != n {
    return None
  }
  Some({ year, month, day, hour, minute, second, frac, tz, })
}

///|
/// 格里历日期 → 自 1970-01-01 起的天数(可为负)。
///
/// Howard Hinnant 的 civil-from-days 逆算法。⚠️ 关键第一步是
/// **`m <= 2` 时先把年份减 1**(把 3 月当成一年的起点),漏掉这一步
/// 会让 1970-01-01 算出 365 而不是 0——差整整一年,且不报错。
/// 地板除。MoonBit 的 `/` 是向零取整,负年份会算错一个 era(整整 400 年)。
fn floor_div(a : Int, b : Int) -> Int {
  let q = a / b
  let r = a % b
  if r != 0 && (r < 0) != (b < 0) {
    q - 1
  } else {
    q
  }
}

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

///|
/// 把 `xsd:dateTime` 转成**可比较的数值**:自 Unix 纪元起的秒数。
///
/// 为什么需要它:判断"轨迹点的时间是否递增"不能靠字符串字典序——
/// `2026-09-17T10:00:00Z` 与 `2026-09-17T18:00:00+08:00` 是同一时刻,
/// 但字典序会认为后者更大。有时区时折算到 UTC 再比。
///
/// 无时区时**按 UTC 处理**(`xsd:dateTime` 允许省略时区,含义是"未知本地时",
/// 本库不做猜测,只保证可比)。
///
/// 自己先做一遍词法校验,非法字面量返回 `None`
/// (`parse_dt_parts` 只抽数字、不查范围,必须先拦住 `25:00:00` 这类)。
pub fn datetime_to_epoch(text : String) -> Double? {
  if !is_valid_datetime(text) {
    return None
  }
  match parse_dt_parts(text) {
    None => None
    Some(p) => {
      let days = days_from_civil(p.year, p.month, p.day)
      let secs = days.to_double() * 86400.0 +
        p.hour.to_double() * 3600.0 +
        p.minute.to_double() * 60.0 +
        p.second.to_double() +
        p.frac
      match p.tz {
        Some(offset_min) => Some(secs - offset_min.to_double() * 60.0)
        None => Some(secs)
      }
    }
  }
}

///|
/// 两个 `xsd:dateTime` 的先后:`-1` = a 早于 b,`0` = 相等,`1` = a 晚于 b。
///
/// 任一方非法(或缺少可比信息)时返回 `None`——**不做猜测**。
pub fn datetime_compare(a : String, b : String) -> Int? {
  match (datetime_to_epoch(a), datetime_to_epoch(b)) {
    (Some(x), Some(y)) =>
      if x < y {
        Some(-1)
      } else if x > y {
        Some(1)
      } else {
        Some(0)
      }
    _ => None
  }
}