///|
/// FILETIME / SYSTEMTIME rendering style.
pub(all) enum TimeStyle {
  PythonEvtx
  Iso8601
} derive(Eq, Debug)

///|
fn pad2(n : Int) -> String {
  if n < 10 {
    "0" + n.to_string()
  } else {
    n.to_string()
  }
}

///|
fn pad4(n : Int) -> String {
  if n < 10 {
    "000" + n.to_string()
  } else if n < 100 {
    "00" + n.to_string()
  } else if n < 1000 {
    "0" + n.to_string()
  } else {
    n.to_string()
  }
}

///|
fn pad6(n : Int) -> String {
  if n < 10 {
    "00000" + n.to_string()
  } else if n < 100 {
    "0000" + n.to_string()
  } else if n < 1000 {
    "000" + n.to_string()
  } else if n < 10000 {
    "00" + n.to_string()
  } else if n < 100000 {
    "0" + n.to_string()
  } else {
    n.to_string()
  }
}

///|
/// Civil date from days since Unix epoch using Howard Hinnant's algorithm.
fn civil_from_unix_days(unix_days : Int64) -> (Int, Int, Int) {
  let z = unix_days + 719468L
  let era = if z >= 0L { z / 146097L } else { (z - 146096L) / 146097L }
  let doe = z - era * 146097L
  let yoe = (doe - doe / 1460L + doe / 36524L - doe / 146096L) / 365L
  let y = yoe + era * 400L
  let doy = doe - (365L * yoe + yoe / 4L - yoe / 100L)
  let mp = (5L * doy + 2L) / 153L
  let d = doy - (153L * mp + 2L) / 5L + 1L
  let m = mp + (if mp < 10L { 3L } else { -9L })
  let year = y + (if m <= 2L { 1L } else { 0L })
  (year.to_int(), m.to_int(), d.to_int())
}

///|
fn civil_from_unix(unix : Int64) -> (Int, Int, Int, Int, Int, Int) {
  let day_secs = 86400L
  let days = if unix >= 0L {
    unix / day_secs
  } else {
    (unix - (day_secs - 1L)) / day_secs
  }
  let sod = (unix - days * day_secs).to_int()
  let hour = sod / 3600
  let minute = sod % 3600 / 60
  let second = sod % 60
  let (year, month, day) = civil_from_unix_days(days)
  (year, month, day, hour, minute, second)
}

///|
/// Convert a Windows FILETIME (100-ns ticks from 1601-01-01) to Unix seconds.
pub fn filetime_to_unix(filetime : UInt64) -> Int64 {
  if filetime == 0UL {
    return -62135596800L
  }
  let seconds = filetime / 10000000UL
  seconds.reinterpret_as_int64() - 11644473600L
}

///|
fn filetime_microseconds(filetime : UInt64) -> Int {
  if filetime == 0UL {
    0
  } else {
    (filetime % 10000000UL / 10UL).to_int()
  }
}

///|
/// python-evtx `datetime.isoformat(" ")` for UTC FILETIME values.
pub fn format_filetime(filetime : UInt64, style : TimeStyle) -> String {
  if filetime == 0UL {
    match style {
      PythonEvtx => "0001-01-01 00:00:00"
      Iso8601 => "0001-01-01T00:00:00Z"
    }
  } else {
    let unix = filetime_to_unix(filetime)
    let us = filetime_microseconds(filetime)
    let (year, month, day, hour, minute, second) = civil_from_unix(unix)
    let date = pad4(year) + "-" + pad2(month) + "-" + pad2(day)
    let clock = pad2(hour) + ":" + pad2(minute) + ":" + pad2(second)
    let frac = if us == 0 { "" } else { "." + pad6(us) }
    match style {
      PythonEvtx => date + " " + clock + frac + "+00:00"
      Iso8601 => date + "T" + clock + frac + "Z"
    }
  }
}

///|
/// Encode Unix seconds as FILETIME.
pub fn unix_to_filetime(unix : Int64) -> UInt64 {
  let ticks = (unix + 11644473600L) * 10000000L
  ticks.reinterpret_as_uint64()
}

///|
/// SYSTEMTIME: year, month, dow, day, hour, minute, second, millisecond.
pub fn format_systemtime(
  data : Bytes,
  pos : Int,
  style : TimeStyle,
) -> Result[String, EvtxError] {
  let _ = match need(data, pos, 16, "systemtime") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (year, _) = match read_u16(data, pos) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (month, _) = match read_u16(data, pos + 2) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (day, _) = match read_u16(data, pos + 6) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (hour, _) = match read_u16(data, pos + 8) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (minute, _) = match read_u16(data, pos + 10) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (second, _) = match read_u16(data, pos + 12) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let (millis, _) = match read_u16(data, pos + 14) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let date = pad4(year) + "-" + pad2(month) + "-" + pad2(day)
  let clock = pad2(hour) + ":" + pad2(minute) + ":" + pad2(second)
  let us = millis * 1000
  let frac = if us == 0 { "" } else { "." + pad6(us) }
  match style {
    PythonEvtx => Ok(date + " " + clock + frac)
    Iso8601 => Ok(date + "T" + clock + frac)
  }
}

///|
pub extend TimeStyle with Eq::{not_equal, equal}

///|
pub extend TimeStyle with @moonbitlang/core/debug.Debug::{to_repr}