///|
/// An exact point on the timeline, as a count of nanoseconds since the Unix
/// epoch.
///
/// An instant carries no calendar and no time zone; it is the same moment
/// everywhere. The representable range is ±10^8 days around the epoch, the
/// same range ECMAScript `Date` covers.
///
/// Reference: 
pub struct Instant {
  epoch_nanoseconds : @int128.Int128
} derive(Eq, Compare)

///|
pub impl Show for Instant with fn output(self, logger) {
  logger.write_string(
    try! self.to_string_with_options(
      ToStringRoundingOptions::default(),
      None,
      utc_only_provider,
    ),
  )
}

///|
pub impl Debug for Instant with fn to_repr(self) {
  Repr::Repr(self.to_string())
}

///|
/// The Unix epoch, 1970-01-01T00:00:00Z.
pub let instant_epoch : Instant = { epoch_nanoseconds: @int128.zero }

///|
/// Creates an instant from nanoseconds since the epoch.
///
/// ```mbt check
/// test {
///   let instant = @temporal.Instant::from_epoch_nanoseconds(
///     @int128.of_int64(1740827770000000000L),
///   )
///   inspect(instant, content="2025-03-01T11:16:10Z")
/// }
/// ```
pub fn Instant::from_epoch_nanoseconds(
  epoch_nanoseconds : @int128.Int128,
) -> Instant raise TemporalError {
  if !is_valid_epoch_nanoseconds(epoch_nanoseconds) {
    raise RangeError("instant is outside the representable epoch range")
  }
  { epoch_nanoseconds, }
}

///|
/// Creates an instant from milliseconds since the epoch.
pub fn Instant::from_epoch_milliseconds(
  epoch_milliseconds : Int64,
) -> Instant raise TemporalError {
  Instant::from_epoch_nanoseconds(
    @int128.of_int64(epoch_milliseconds).mul(i128_million),
  )
}

///|
/// Returns whether the count is inside the representable instant range.
pub fn is_valid_epoch_nanoseconds(nanoseconds : @int128.Int128) -> Bool {
  ns_min_instant.compare(nanoseconds) <= 0 &&
  nanoseconds.compare(ns_max_instant) <= 0
}

///|
/// Returns the nanoseconds since the epoch.
pub fn Instant::epoch_nanoseconds(self : Instant) -> @int128.Int128 {
  self.epoch_nanoseconds
}

///|
/// Returns the milliseconds since the epoch, rounded toward negative infinity.
pub fn Instant::epoch_milliseconds(self : Instant) -> Int64 {
  self.epoch_nanoseconds.div_euclid(i128_million).to_int64_saturating()
}

///|
/// Adds a duration.
///
/// The duration may only have time components: an instant has no calendar, so
/// years, months, weeks and days have no defined length against it.
pub fn Instant::add(
  self : Instant,
  duration : Duration,
) -> Instant raise TemporalError {
  if duration.years != 0L ||
    duration.months != 0L ||
    duration.weeks != 0L ||
    duration.days != 0L {
    raise RangeError(
      "an Instant can only be shifted by time units, not calendar units",
    )
  }
  Instant::from_epoch_nanoseconds(
    self.epoch_nanoseconds.add(
      TimeDuration::from_duration(duration).nanoseconds(),
    ),
  )
}

///|
/// Subtracts a duration.
pub fn Instant::subtract(
  self : Instant,
  duration : Duration,
) -> Instant raise TemporalError {
  self.add(duration.negated())
}

///|
/// `DifferenceTemporalInstant`.
fn Instant::diff_instant(
  self : Instant,
  operation : DifferenceOperation,
  other : Instant,
  settings : DifferenceSettings,
) -> Duration raise TemporalError {
  let resolved = ResolvedRoundingOptions::from_diff_settings(
    settings,
    operation,
    UnitGroup::Time,
    Second,
    Nanosecond,
  )
  if resolved.largest_unit.is_calendar_unit() || resolved.largest_unit is Day {
    raise RangeError(
      "the largest unit for an Instant difference must be a time unit",
    )
  }
  let mut time = TimeDuration::from_nanosecond_difference(
    other.epoch_nanoseconds,
    self.epoch_nanoseconds,
  )
  if !resolved.is_noop() {
    time = time.round(resolved)
  }
  let result = Duration::from_internal(
    InternalDurationRecord::combine(DateDuration::default(), time),
    resolved.largest_unit,
  )
  match operation {
    Until => result
    Since => result.negated()
  }
}

///|
/// Returns the duration from this instant until `other`.
pub fn Instant::until(
  self : Instant,
  other : Instant,
  settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
  self.diff_instant(Until, other, settings)
}

///|
/// Returns the duration from `other` until this instant.
pub fn Instant::since(
  self : Instant,
  other : Instant,
  settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
  self.diff_instant(Since, other, settings)
}

///|
/// Rounds the instant to a multiple of the given unit.
///
/// Rounding is done as though the instant were positive, so that moments
/// before and after the epoch round in the same direction.
pub fn Instant::round(
  self : Instant,
  options : RoundingOptions,
) -> Instant raise TemporalError {
  let resolved = ResolvedRoundingOptions::from_instant_options(options)
  let length = temporal_unwrap(
    resolved.smallest_unit.as_nanoseconds(),
    "time unit length",
  )
  let increment = @int128.of_int64(length).mul(
    @int128.of_int(resolved.increment.get()),
  )
  let rounded = IncrementRounder::from_signed_num(
    self.epoch_nanoseconds,
    increment,
  ).round_as_if_positive(resolved.rounding_mode)
  Instant::from_epoch_nanoseconds(rounded)
}

///|
/// Converts to a `ZonedDateTime` in the given time zone.
pub fn[P : TimeZoneProvider] Instant::to_zoned_date_time(
  self : Instant,
  time_zone : TimeZone,
  provider : P,
  calendar? : Calendar = Calendar::ISO,
) -> ZonedDateTime raise TemporalError {
  ZonedDateTime::try_new(self.epoch_nanoseconds, time_zone, provider, calendar~)
}

///|
/// Parses an instant from an RFC 9557 string, which must carry a UTC offset.
///
/// ```mbt check
/// test {
///   inspect(
///     @temporal.Instant::of_string("2025-03-01T11:16:10+01:00"),
///     content="2025-03-01T10:16:10Z",
///   )
/// }
/// ```
pub fn Instant::of_string(source : String) -> Instant raise TemporalError {
  let (date, time, offset) = parse_instant_string(source)
  let iso_date = IsoDate::new_with_overflow(
    date.year,
    date.month,
    date.day,
    Reject,
  )
  let iso = IsoDateTime::new_unchecked(iso_date, iso_time_of_parsed(time))
  let offset_nanoseconds = match offset {
    ZDesignator => 0L
    NumericOffset(value) => value.nanoseconds()
  }
  Instant::from_epoch_nanoseconds(
    iso.as_nanoseconds().sub(@int128.of_int64(offset_nanoseconds)),
  )
}

///|
/// Renders the instant in RFC 9557 form.
///
/// Without a time zone the instant is rendered in UTC with a `Z` suffix;
/// with one, it is rendered as a local time with that zone's offset.
pub fn[P : TimeZoneProvider] Instant::to_string_with_options(
  self : Instant,
  options : ToStringRoundingOptions,
  time_zone : TimeZone?,
  provider : P,
) -> String raise TemporalError {
  let resolved = options.resolve()
  let rounding = ResolvedRoundingOptions::from_to_string_options(resolved)
  let length = temporal_unwrap(
    rounding.smallest_unit.as_nanoseconds(),
    "time unit length",
  )
  let increment = @int128.of_int64(length).mul(
    @int128.of_int(rounding.increment.get()),
  )
  let rounded = IncrementRounder::from_signed_num(
    self.epoch_nanoseconds,
    increment,
  ).round_as_if_positive(rounding.rounding_mode)
  let offset_nanoseconds = match time_zone {
    None => 0L
    Some(zone) => zone.offset_nanoseconds_for(rounded, provider)
  }
  let iso = IsoDateTime::from_epoch_nanoseconds(rounded, offset_nanoseconds)
  let buf = StringBuilder::new()
  write_date(buf, iso.date.year, iso.date.month, iso.date.day)
  buf.write_string("T")
  write_time(
    buf,
    iso.time.hour,
    iso.time.minute,
    iso.time.second,
    iso.time.subsecond_nanoseconds(),
    resolved.precision,
    true,
  )
  match time_zone {
    None => buf.write_string("Z")
    Some(_) => buf.write_string(UtcOffset(offset_nanoseconds).to_string())
  }
  buf.to_string()
}

///|
/// Renders the instant in UTC, as `YYYY-MM-DDTHH:MM:SSZ`.
pub fn Instant::to_string(self : Instant) -> String {
  try! self.to_string_with_options(
    ToStringRoundingOptions::default(),
    None,
    utc_only_provider,
  )
}

///|
/// `DifferenceInstant`: the rounded difference between two instants, with the
/// rounding options already resolved.
fn Instant::diff_internal(
  self : Instant,
  other : Instant,
  options : ResolvedRoundingOptions,
) -> InternalDurationRecord raise TemporalError {
  let mut time = TimeDuration::from_nanosecond_difference(
    other.epoch_nanoseconds,
    self.epoch_nanoseconds,
  )
  if !options.is_noop() {
    time = time.round(options)
  }
  InternalDurationRecord::combine(DateDuration::default(), time)
}