///|
pub(all) struct Scheduled {
  ticket : UInt64
  due : TimeTag
  message : Message
} derive(Eq, Debug)

///|
pub extend Scheduled with Eq::{equal, not_equal}

///|
pub extend Scheduled with @debug.Debug::{to_repr}

///|
pub struct Scheduler {
  priv heap : Array[Scheduled]
  priv capacity : Int32
  priv mut next_ticket : UInt64
}

///|
pub fn Scheduler::new(capacity~ : Int32 = 4096) -> Scheduler raise OscError {
  if capacity < 1 || capacity > 65536 {
    raise Limit("queue capacity must be 1..65536")
  }
  { heap: [], capacity, next_ticket: 1UL, }
}

///|
fn earlier(a : Scheduled, b : Scheduled) -> Bool {
  a.due.bits() < b.due.bits() || (a.due == b.due && a.ticket < b.ticket)
}

///|
fn Scheduler::insert(self : Scheduler, event : Scheduled) -> Unit {
  self.heap.push(event)
  let mut i = self.heap.length() - 1
  while i > 0 {
    let p = (i - 1) / 2
    if !earlier(self.heap[i], self.heap[p]) {
      break
    }
    self.heap.swap(i, p)
    i = p
  }
}

///|
fn Scheduler::sift_down(self : Scheduler, start : Int32) -> Unit {
  let mut i = start
  while i < self.heap.length() / 2 {
    let left = 2 * i + 1
    let right = left + 1
    let best = if right < self.heap.length() &&
      earlier(self.heap[right], self.heap[left]) {
      right
    } else {
      left
    }
    if !earlier(self.heap[best], self.heap[i]) {
      break
    }
    self.heap.swap(i, best)
    i = best
  }
}

///|
fn Scheduler::take(self : Scheduler) -> Scheduled {
  let first = self.heap[0]
  let last = self.heap.pop().unwrap()
  if !self.heap.is_empty() {
    self.heap[0] = last
    self.sift_down(0)
  }
  first
}

///|
/// Admission is transactional: flatten and all capacities checked before mutation.
pub fn Scheduler::enqueue(
  self : Scheduler,
  packet : Packet,
  now : TimeTag,
  limits~ : Limits = Limits::default(),
) -> Array[UInt64] raise OscError {
  let events = flatten(packet, now, limits~)
  if events.length() > self.capacity - self.heap.length() {
    raise Limit("queue capacity")
  }
  if events.length().to_uint64() > 0xffffffffffffffffUL - self.next_ticket {
    raise Limit("ticket counter exhausted")
  }
  let ids : Array[UInt64] = []
  for event in events {
    let ticket = self.next_ticket
    self.next_ticket += 1UL
    self.insert({ ticket, due: event.due, message: event.message, })
    ids.push(ticket)
  }
  ids
}

///|
pub fn Scheduler::poll(
  self : Scheduler,
  now : TimeTag,
  max_count~ : Int32 = 4096,
) -> Array[Scheduled] raise OscError {
  if now.is_immediate() {
    raise Invalid("poll requires absolute now", 0)
  }
  if max_count < 0 || max_count > 65536 {
    raise Limit("poll count must be 0..65536")
  }
  let out : Array[Scheduled] = []
  while !self.heap.is_empty() && out.length() < max_count {
    if self.heap[0].due.bits() > now.bits() {
      break
    }
    out.push(self.take())
  }
  out
}

///|
pub fn Scheduler::len(self : Scheduler) -> Int32 {
  self.heap.length()
}

///|
pub fn Scheduler::next_due(self : Scheduler) -> TimeTag? {
  if self.heap.is_empty() {
    None
  } else {
    Some(self.heap[0].due)
  }
}

///|
/// Cancel one ticket. Removing a non-root node restores the heap in both directions.
pub fn Scheduler::cancel(self : Scheduler, ticket : UInt64) -> Bool {
  let mut index = -1
  for i in 0.. 0 {
      let parent = (i - 1) / 2
      if !earlier(self.heap[i], self.heap[parent]) {
        break
      }
      self.heap.swap(i, parent)
      i = parent
    }
    self.sift_down(i)
  }
  true
}

///|
/// Explicit late-event policy: discard events strictly before cutoff, returning them
/// in stable order. Equality is retained. Validation happens before any mutation.
pub fn Scheduler::discard_before(
  self : Scheduler,
  cutoff : TimeTag,
) -> Array[Scheduled] raise OscError {
  if cutoff.is_immediate() {
    raise Invalid("cutoff requires absolute time", 0)
  }
  let out : Array[Scheduled] = []
  while !self.heap.is_empty() && self.heap[0].due.bits() < cutoff.bits() {
    out.push(self.take())
  }
  out
}

///|
/// Clear pending messages without recycling ticket identities.
pub fn Scheduler::clear(self : Scheduler) -> Int32 {
  let count = self.heap.length()
  self.heap.clear()
  count
}