///|
/// Matplotlib MicrosecondLocator with a finite positive Double interval.
/// Date conversion precedes the midnight-relative MultipleLocator grid.
pub struct MicrosecondLocator {
  priv interval : Double
  priv timezone : DateTimeZone
  priv context : DateParserContext?
}

///|
/// Capture the display zone before validating the interval. context is only
/// used for host interpretation of naive values in a first-aware mixed array.
///
/// ```mbt check
/// test {
///   let locator = @date.MicrosecondLocator::new(interval=0.5, tz="UTC")
///   let first = @date.RRuleDateTime::naive(@date.datetime(1970, 1, 1))
///   let last = @date.RRuleDateTime::naive(
///     @date.datetime(1970, 1, 1, microsecond=3),
///   )
///   assert_eq(locator.tick_values(first, last).length(), 9)
///   assert_eq(locator.as_locator().interval_value(), 0.5)
/// }
/// ```
pub fn MicrosecondLocator::new(
  interval~ : Double = 1.0,
  tz? : String,
  timezone? : DateTimeZone,
  context? : DateParserContext,
) -> MicrosecondLocator raise DateError {
  let timezone = selected_timezone(tz, timezone)
  if interval.is_nan() || interval.is_inf() || interval <= 0.0 {
    raise DateError::InvalidDate(
      message="microsecond interval must be finite and positive",
    )
  }
  { interval, timezone, context, }
}

///|
pub fn MicrosecondLocator::interval(self : MicrosecondLocator) -> Double {
  self.interval
}

///|
pub fn MicrosecondLocator::unit_scale(_self : MicrosecondLocator) -> Double {
  1.0 / 86_400_000_000.0
}

///|
pub fn MicrosecondLocator::timezone(self : MicrosecondLocator) -> DateTimeZone {
  self.timezone
}

///|
pub fn MicrosecondLocator::with_timezone(
  self : MicrosecondLocator,
  timezone : DateTimeZone,
) -> MicrosecondLocator {
  { ..self, timezone, }
}

///|
pub fn MicrosecondLocator::with_offset(
  self : MicrosecondLocator,
  offset_minutes : Int32,
) -> MicrosecondLocator raise DateError {
  self.with_timezone(fixed_timezone(offset_minutes))
}

///|
/// Return source-order ascending ticks, including neighboring points outside
/// years 1..9999. Direct reversed bounds keep the original first origin.
pub fn MicrosecondLocator::tick_values(
  self : MicrosecondLocator,
  first : RRuleDateTime,
  last : RRuleDateTime,
) -> Array[Double] raise DateError {
  let numbers = date_values_to_numbers([first, last], self.context)
  microsecond_ticks_unclipped(numbers[0], numbers[1], self.interval)
}

///|
fn microsecond_interval_projection(interval : Double) -> Int32 {
  Double::min(2147483647.0, interval).to_int()
}

///|
/// Adapt the exact interval while retaining the legacy integer projection.
pub fn MicrosecondLocator::as_locator(self : MicrosecondLocator) -> DateLocator {
  {
    kind: Microseconds,
    interval: microsecond_interval_projection(self.interval),
    selector: 0,
    secondary: 0,
    selector_enabled: false,
    offset_minutes: self.timezone.initial_offset_minutes(),
    timezone: Some(self.timezone),
    selection: None,
    secondary_selection: None,
    holidays: [],
    rule: None,
    automatic: None,
    wrapper_locator: None,
    microsecond_object: Some(self),
  }
}

///|
/// Return the exact interval, including fractional microseconds.
pub fn DateLocator::interval_value(self : DateLocator) -> Double {
  match self.microsecond_object {
    Some(value) => value.interval
    None => Double::from_int(self.interval())
  }
}

///|
pub fn DateLocator::microsecond_locator(
  self : DateLocator,
) -> MicrosecondLocator? {
  self.microsecond_object
}

///|
pub fn MicrosecondLocator::nonsingular(
  self : MicrosecondLocator,
  lower : Double,
  upper : Double,
) -> (Double, Double) {
  self.as_locator().nonsingular(lower, upper)
}

///|
fn MicrosecondLocator::numeric_tick_values(
  self : MicrosecondLocator,
  lower : Double,
  upper : Double,
) -> Array[Double] raise DateError {
  // Inverse conversion selects epoch even when nonfinite input then fails.
  if lower.is_nan() || lower.is_inf() || upper.is_nan() || upper.is_inf() {
    ignore(selected_epoch())
    raise DateError::InvalidDate(
      message="microsecond numeric bounds must be finite",
    )
  }
  let first = RRuleDateTime::aware(
    self.timezone.to_datetime(Double::min(lower, upper)),
  )
  let last = RRuleDateTime::aware(
    self.timezone.to_datetime(Double::max(lower, upper)),
  )
  let values = self.tick_values(first, last)
  if lower <= upper {
    values
  } else {
    values.rev()
  }
}