///|
fn RRuleWrapper::normalize_query(
  self : RRuleWrapper,
  value : RRuleDateTime,
) -> RRuleDateTime raise DateError {
  match self.timezone {
    Some(zone) => RRuleDateTime::naive(value.wall_in(zone))
    None => value
  }
}

///|
fn wrapper_compare(
  candidate : DateTime,
  target : RRuleDateTime,
) -> Int64 raise DateError {
  if !target.is_naive() {
    raise DateError::InvalidDate(
      message="cannot compare an aware argument with a naive recurrence",
    )
  }
  locator_microseconds(candidate) - locator_microseconds(target.value)
}

///|
fn RRuleWrapper::return_one(
  self : RRuleWrapper,
  value : DateTime?,
) -> RRuleDateTime? raise DateError {
  match value {
    Some(value) => Some(attach_rule_timezone(value, self.timezone))
    None if self.timezone is Some(_) =>
      raise DateError::InvalidDate(
        message="aware recurrence query returned no datetime to attach",
      )
    None => None
  }
}

///|
/// First recurrence after a date, or at the date when inc=true. An aware
/// wrapper raises when no match exists, matching the original attach(None).
pub fn RRuleWrapper::after(
  self : RRuleWrapper,
  value : RRuleDateTime,
  inc~ : Bool = false,
) -> RRuleDateTime? raise DateError {
  let query = self.normalize_query(value)
  let lower = if query.is_naive() { Some(query.value) } else { None }
  let reader = self.checked_reader(lower?)
  while reader.next_date() is Some(candidate) {
    let comparison = wrapper_compare(candidate, query)
    if comparison > 0L || (inc && comparison == 0L) {
      return self.return_one(Some(candidate))
    }
  }
  self.return_one(None)
}

///|
/// Eager typed xafter companion. It attaches the effective zone when present;
/// raw_iter exposes the underlying naive stream. Eager arrays stop at 100000.
pub fn RRuleWrapper::xafter(
  self : RRuleWrapper,
  value : RRuleDateTime,
  count? : Int32,
  inc~ : Bool = false,
) -> Array[RRuleDateTime] raise DateError {
  let query = self.normalize_query(value)
  let lower = if query.is_naive() { Some(query.value) } else { None }
  let result : Array[RRuleDateTime] = []
  let mut seen = 0L
  let reader = self.checked_reader(lower?)
  while reader.next_date() is Some(candidate) {
    let comparison = wrapper_compare(candidate, query)
    if comparison > 0L || (inc && comparison == 0L) {
      seen += 1L
      if count is Some(count) && seen > Int64::from_int(count) {
        break
      }
      result.push(attach_rule_timezone(candidate, self.timezone))
      if result.length() >= 100000 {
        break
      }
    }
  }
  result
}

///|
/// Recurrences between local-normalized bounds. Both ends are exclusive by
/// default. The underlying comparison order is before, then after.
pub fn RRuleWrapper::between(
  self : RRuleWrapper,
  after : RRuleDateTime,
  before : RRuleDateTime,
  inc~ : Bool = false,
) -> Array[RRuleDateTime] raise DateError {
  let lower = self.normalize_query(after)
  let upper = self.normalize_query(before)
  let (first, last) = if lower.is_naive() && upper.is_naive() {
    (Some(lower.value), Some(upper.value))
  } else {
    (None, None)
  }
  let result : Array[RRuleDateTime] = []
  let reader = self.checked_reader(lower?=first, upper?=last)
  while reader.next_date() is Some(candidate) {
    let last_comparison = wrapper_compare(candidate, upper)
    if last_comparison > 0L || (!inc && last_comparison == 0L) {
      break
    }
    let first_comparison = wrapper_compare(candidate, lower)
    if first_comparison > 0L || (inc && first_comparison == 0L) {
      result.push(attach_rule_timezone(candidate, self.timezone))
      if result.length() >= 100000 {
        break
      }
    }
  }
  result
}

///|
fn RRuleWrapper::find_naive_before(
  self : RRuleWrapper,
  value : RRuleDateTime,
  inc : Bool,
) -> DateTime? raise DateError {
  let target = locator_microseconds(value.value)
  let start = locator_microseconds(self.core.dtstart())
  if target < start {
    return None
  }
  let mut span = Int64::from_int(self.core.before_probe_days()) * 86400000000L
  while true {
    let first = Int64::max(start, target - span)
    let lower = locator_datetime_microseconds(first, 0)
    let mut result : DateTime? = None
    let reader = self.checked_reader(lower~, upper=value.value)
    while reader.next_date() is Some(candidate) {
      let comparison = wrapper_compare(candidate, value)
      if comparison > 0L || (!inc && comparison == 0L) {
        break
      }
      result = Some(candidate)
    }
    if result is Some(_) || first <= start {
      return result
    }
    span = Int64::min(315537897600000000L, span * 2L)
  }
  None
}

///|
pub fn RRuleWrapper::before(
  self : RRuleWrapper,
  value : RRuleDateTime,
  inc~ : Bool = false,
) -> RRuleDateTime? raise DateError {
  let query = self.normalize_query(value)
  // The optimized before path can return without reading any candidate.
  self.core.check_clock_iteration()
  if query.is_naive() && self.core.rule.count is None && self.core.cache is None {
    return self.return_one(self.find_naive_before(query, inc))
  }
  let mut result : DateTime? = None
  let reader = self.checked_reader()
  while reader.next_date() is Some(candidate) {
    let comparison = wrapper_compare(candidate, query)
    if comparison > 0L || (!inc && comparison == 0L) {
      break
    }
    result = Some(candidate)
  }
  self.return_one(result)
}