///|
/// Tracks callouts across listing blocks and callout lists (Ruby `Callouts`).
pub struct Callouts {
  priv lists : Array[Array[(Int, String)]]
  priv mut list_index : Int
  priv mut co_index : Int
}

///|
pub fn Callouts::new() -> Callouts {
  let c = { lists: [], list_index: 0, co_index: 1, }
  c.next_list()
  c
}

///|
/// Registers a callout with the given ordinal; returns its id.
pub fn Callouts::register(self : Callouts, li_ordinal : String) -> String {
  let id = "CO\{self.list_index}-\{self.co_index}"
  self.current_list().push((@rb.to_i(li_ordinal), id))
  self.co_index += 1
  id
}

///|
/// Reads the id of the next callout in the current list.
pub fn Callouts::read_next_id(self : Callouts) -> String? {
  let list = self.current_list()
  let id = if self.co_index <= list.length() {
    Some(list[self.co_index - 1].1)
  } else {
    None
  }
  self.co_index += 1
  id
}

///|
/// Space-separated ids of the callouts with the given ordinal.
pub fn Callouts::callout_ids(self : Callouts, li_ordinal : Int) -> String {
  self
  .current_list()
  .filter(item => item.0 == li_ordinal)
  .map(item => item.1)
  .join(" ")
}

///|
fn Callouts::current_list(self : Callouts) -> Array[(Int, String)] {
  self.lists[self.list_index - 1]
}

///|
pub fn Callouts::next_list(self : Callouts) -> Unit {
  self.list_index += 1
  if self.lists.length() < self.list_index {
    self.lists.push([])
  }
  self.co_index = 1
}

///|
pub fn Callouts::rewind(self : Callouts) -> Unit {
  self.list_index = 1
  self.co_index = 1
}