///|
// Clock fields at or above the recurrence frequency filter base positions.
// Smaller fields expand within a base period in recurrence_clock_times.
fn DateRule::matches_clock_unit(
  self : DateRule,
  slot : Int32,
  unit : Int32,
) -> Bool {
  let seconds = slot * unit
  recurrence_contains(self.byhour, seconds / 3600) &&
  (unit >= 3600 || recurrence_contains(self.byminute, seconds % 3600 / 60)) &&
  (unit >= 60 || recurrence_contains(self.bysecond, seconds % 60))
}

///|
fn recurrence_clock_times(
  rule : DateRule,
  anchor : DateTime,
  unit : Int32,
) -> Array[Int32] {
  let times : Array[Int32] = []
  let minutes = if unit == 3600 {
    recurrence_values(rule.byminute, anchor.minute)
  } else {
    [0]
  }
  let seconds = if unit >= 60 {
    recurrence_values(rule.bysecond, anchor.second)
  } else {
    [0]
  }
  for minute in minutes {
    for second in seconds {
      times.push(minute * 60 + second)
    }
  }
  match rule.bysetpos {
    Some(positions) =>
      recurrence_indices(positions, times.length()).map(index => times[index])
    None => times
  }
}

///|
fn recurrence_clock_candidates(
  rule : DateRule,
  window : RecurrenceCalendarWindow,
  unit : Int32,
) -> Iter[Int64] {
  let unit64 = Int64::from_int(unit)
  let units_per_day = 86400 / unit
  let units64 = Int64::from_int(units_per_day)
  let step = Int64::from_int(rule.interval)
  let anchor_unit = locator_floor_div64(locator_seconds(window.anchor), unit64)
  let times = recurrence_clock_times(rule, window.anchor, unit)
  if times.is_empty() {
    return Iter::empty()
  }
  let slots = recurrence_clock_slots(rule, window.anchor, unit).to_array()
  if slots.is_empty() {
    return Iter::empty()
  }
  let first_day = if rule.count is Some(_) {
    Int64::min(
      locator_floor_div64(locator_seconds(window.first_date), 86400L),
      locator_floor_div64(locator_seconds(window.anchor), 86400L),
    )
  } else {
    locator_floor_div64(locator_seconds(window.first_date), 86400L)
  }
  let last_day = locator_floor_div64(locator_seconds(window.last_date), 86400L)
  let mut day = first_day
  let mut current : Iter[Int64] = Iter::empty()
  Iter::new(fn() {
    while true {
      match current.next() {
        Some(value) => return Some(value)
        None => ()
      }
      if day > last_day {
        return None
      }
      let start_unit = day * units64
      let end_unit = start_unit + units64
      let first_unit = locator_align(start_unit, anchor_unit, step)
      if first_unit >= end_unit {
        day = locator_floor_div64(first_unit, units64)
        continue
      }
      let date = locator_datetime_seconds(
        day * 86400L,
        window.anchor.offset_seconds,
      )
      day += 1L
      if !rule.matches_date(date, window.anchor, date.year) {
        continue
      }
      let count = (end_unit - 1L - first_unit) / step + 1L
      let bases = if count <= Int64::from_int(slots.length()) {
        let mut next = first_unit
        Iter::new(fn() {
          while next < end_unit {
            let value = next
            next += step
            if rule.matches_clock_unit((value - start_unit).to_int(), unit) {
              return Some(value)
            }
          }
          None
        })
      } else {
        slots
        .iter()
        .map(slot => start_unit + Int64::from_int(slot))
        .filter(value => {
          value >= anchor_unit && (value - anchor_unit) % step == 0L
        })
      }
      current = bases.flat_map(base => {
        times.iter().map(time => base * unit64 + Int64::from_int(time))
      })
    }
    None
  })
}

///|
// Clock phases repeat over one day. Iterate only the anchor's residue modulo
// gcd(interval, units_per_day), sharing the same mask with checked wrappers.
fn recurrence_clock_slots(
  rule : DateRule,
  anchor : DateTime,
  unit : Int32,
) -> Iter[Int32] {
  let units_per_day = 86400 / unit
  let divisor = locator_gcd(rule.interval, units_per_day)
  let anchor_unit = locator_floor_div64(
    locator_seconds(anchor),
    Int64::from_int(unit),
  )
  let remainder = anchor_unit % Int64::from_int(divisor)
  let mut slot = ((remainder + Int64::from_int(divisor)) %
  Int64::from_int(divisor)).to_int()
  Iter::new(fn() {
    while slot < units_per_day {
      let value = slot
      slot += divisor
      if rule.matches_clock_unit(value, unit) {
        return Some(value)
      }
    }
    None
  })
}