///|
/// Select a concrete calendar locator from local DateTime endpoints. Reuses
/// the public recurrence wrapper, captured timezone and microsecond generators.
pub fn AutoDateLocator::get_locator(
  self : AutoDateLocator,
  first : DateTime,
  last : DateTime,
) -> DateLocator raise DateError {
  let nums = automatic_distances(first, last)
  let ranges : Array[Array[Int32]?] = [
    None,
    Some([1]),
    Some([1]),
    Some([0]),
    Some([0]),
    Some([0]),
    None,
  ]
  let warnings : Array[AutoDateWarning] = []
  let mut index = 6
  let mut interval = 1
  for current in 0..<7 {
    if nums[current] < Double::from_int(self.settings.minticks) {
      ranges[current] = None
      continue
    }
    index = current
    let intervals = self.settings.intervals[current]
    interval = intervals[intervals.length() - 1]
    let mut matched = false
    for candidate in intervals {
      if nums[current] <=
        Double::from_int(candidate) *
        Double::from_int(self.settings.maxticks[current] - 1) {
        interval = candidate
        matched = true
        break
      }
    }
    if !matched && !(self.settings.multiples && current == 2) {
      warnings.push(IntervalFallback(auto_frequency(current), interval))
    }
    self.state.frequency = auto_frequency(current)
    break
  }
  self.state.interval = interval
  let mut actual_interval = interval
  if index >= 1 && index <= 5 {
    let start = if index < 3 { 1 } else { 0 }
    let length = match index {
      1 => 12
      2 => 31
      3 => 24
      _ => 60
    }
    let full = Array::makei(length, offset => start + offset)
    if self.settings.multiples {
      ranges[index] = Some(
        full.filter(value => (value - start) % interval == 0),
      )
      // The fixed source tests the range index against DAILY/WEEKLY constants,
      // so preserve the same special handling for custom hourly 7/14 tables.
      if index == 2 || index == 3 {
        if interval == 14 {
          ranges[index] = Some([1, 15])
        }
        if interval == 7 {
          ranges[index] = Some([1, 8, 15, 22])
        }
      }
      actual_interval = 1
    } else {
      ranges[index] = Some(full)
    }
  }
  let selected = if index == 0 && self.settings.multiples {
    YearLocator::new(
      base=actual_interval,
      tz="UTC",
      context?=self.settings.context,
    ).as_locator()
  } else if index < 6 {
    let frequency = match index {
      0 => Yearly
      1 => Monthly
      2 => Daily
      3 => Hourly
      4 => Minutely
      _ => Secondly
    }
    let bymonth = ranges[1]
    let bymonthday = ranges[2]
    let byhour = ranges[3]
    let byminute = ranges[4]
    let bysecond = ranges[5]
    let wrapper = RRuleWrapper::new(
      frequency,
      dtstart=RRuleDateTime::aware(first),
      until=RRuleDateTime::aware(last),
      interval=actual_interval,
      bymonth?,
      bymonthday?,
      byhour?,
      byminute?,
      bysecond?,
    )
    RRuleLocator::new(wrapper, tz="UTC").as_locator()
  } else {
    if first.to_ordinal() > 70.0 * 365.0 && actual_interval < 1000 {
      warnings.push(DistantMicroseconds)
    }
    MicrosecondLocator::new(
      interval=Double::from_int(actual_interval),
      tz="UTC",
      context?=self.settings.context,
    ).as_locator()
  }
  self.state.warnings = warnings
  match self.settings.timezone {
    Some(zone) => selected.with_timezone(zone)
    None => selected.with_offset(self.settings.offset_minutes)
  }
}

///|
fn AutoDateLocator::date_at(
  self : AutoDateLocator,
  value : Double,
) -> DateTime raise DateError {
  match self.settings.timezone {
    Some(zone) => zone.to_datetime(value)
    None => from_ordinal(value, offset_minutes=self.settings.offset_minutes)
  }
}

///|
fn AutoDateLocator::range_dates(
  self : AutoDateLocator,
  lower : Double,
  upper : Double,
) -> (DateTime, DateTime) raise DateError {
  ignore(selected_epoch())
  // Floating min/max may discard NaN on some targets, so validate before sorting.
  if lower.is_nan() || upper.is_nan() || lower.is_inf() || upper.is_inf() {
    raise DateError::InvalidDate(message="automatic date bounds must be finite")
  }
  (
    self.date_at(Double::min(lower, upper)),
    self.date_at(Double::max(lower, upper)),
  )
}

///|
/// Select from numeric UTC day bounds, interpreting both in this locator's zone.
pub fn AutoDateLocator::get_locator_for_range(
  self : AutoDateLocator,
  lower : Double,
  upper : Double,
) -> DateLocator raise DateError {
  let (first, last) = self.range_dates(lower, upper)
  self.get_locator(first, last)
}

///|
/// Generate ticks in the port's input range order. Singular inputs are passed
/// directly to selection; nonsingular() is a separate autoscale operation.
pub fn AutoDateLocator::tick_values(
  self : AutoDateLocator,
  lower : Double,
  upper : Double,
) -> Array[Double] raise DateError {
  let (first, last) = self.range_dates(lower, upper)
  let selected = self.get_locator(first, last)
  if selected.microsecond_object is Some(locator) {
    let values = locator.tick_values(
      RRuleDateTime::aware(first),
      RRuleDateTime::aware(last),
    )
    return if lower <= upper { values } else { values.rev() }
  }
  if selected.wrapper_locator is Some(locator) {
    // These endpoints already passed through public num2date rounding. A second
    // inverse conversion would add a fractional epoch displacement again.
    let values = locator.tick_values(
      RRuleDateTime::aware(first),
      RRuleDateTime::aware(last),
    )
    return if lower <= upper { values } else { values.rev() }
  }
  selected.ticks_for_dates(
    first.to_ordinal(),
    last.to_ordinal(),
    first,
    last,
    lower <= upper,
  )
}