// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// The three time types the cross-language format carries, wired to moonbitstack's
// calendar types so a MoonBit program never has to spell out epoch seconds itself.
//
//   DATE      days since 1970-01-01, a signed varint64
//   TIMESTAMP seconds since the epoch, int64, then nanoseconds, uint32
//   DURATION  whole seconds, signed varint64, then nanoseconds, int32
//
// The nanosecond field is the sub-second part, always in [0, 1e9), so a value of
// 1.5 seconds is (1, 500_000_000) and never (0, 1_500_000_000).

///|
/// The day count of a date, as the wire carries it: days since 1970-01-01.
pub fn date_of_days(days : Int64) -> @moondate.Date {
  @moondate.Date::of_days(days.to_int())
}

///|
/// The days since 1970-01-01 for a date.
pub fn days_of_date(date : @moondate.Date) -> Int64 {
  date.days().to_int64()
}

///|
/// The whole seconds and the sub-second nanoseconds of a moment.
pub fn parts_of_moment(moment : @moondate.Moment) -> (Int64, Int32) {
  let seconds = match moment.epoch() {
    Some(s) => s
    None => 0L
  }
  let nanos = (moment.time().nanos() % 1_000_000_000L).to_int()
  (seconds, nanos)
}

///|
/// The moment `seconds` after the epoch, plus `nanos` nanoseconds.
pub fn moment_of_parts(seconds : Int64, nanos : Int32) -> @moondate.Moment {
  @moondate.Moment::of_epoch(seconds, nano=nanos)
}

///|
/// The whole seconds and the sub-second nanoseconds of a span. The nanoseconds are
/// normalised into [0, 1e9), borrowing a second when the span is negative.
pub fn parts_of_span(span : @moondate.Span) -> (Int64, Int32) {
  let n = span.nanos
  let mut seconds = n / 1_000_000_000L
  let mut nanos = n % 1_000_000_000L
  if nanos < 0L {
    nanos = nanos + 1_000_000_000L
    seconds = seconds - 1L
  }
  (seconds, nanos.to_int())
}

///|
/// The span from `seconds` and `nanos`.
pub fn span_of_parts(seconds : Int64, nanos : Int32) -> @moondate.Span {
  @moondate.Span::new(seconds~, nanos=nanos.to_int64())
}

///|
/// A date travels as DATE.
pub impl Codec for @moondate.Date with fn ty(_self) {
  TypeId::Date
}

///|
pub impl Codec for @moondate.Date with fn write_data(self, w) {
  w.write_var_i64(days_of_date(self))
}

///|
pub impl Codec for @moondate.Date with fn read_data(_r, _ty) {
  date_of_days(_r.read_var_i64())
}

///|
/// A moment travels as TIMESTAMP.
pub impl Codec for @moondate.Moment with fn ty(_self) {
  TypeId::Timestamp
}

///|
pub impl Codec for @moondate.Moment with fn write_data(self, w) {
  let (seconds, nanos) = parts_of_moment(self)
  w.write_i64(seconds)
  w.write_u32(nanos.reinterpret_as_uint())
}

///|
pub impl Codec for @moondate.Moment with fn read_data(r, _ty) {
  let seconds = r.read_i64()
  let nanos = r.read_u32().reinterpret_as_int()
  moment_of_parts(seconds, nanos)
}

///|
/// A span travels as DURATION.
pub impl Codec for @moondate.Span with fn ty(_self) {
  TypeId::Duration
}

///|
pub impl Codec for @moondate.Span with fn write_data(self, w) {
  let (seconds, nanos) = parts_of_span(self)
  w.write_var_i64(seconds)
  w.write_i32(nanos)
}

///|
pub impl Codec for @moondate.Span with fn read_data(r, _ty) {
  let seconds = r.read_var_i64()
  let nanos = r.read_i32()
  span_of_parts(seconds, nanos)
}