///|
fn info(id : Int32, node : Node) -> NodeInfo {
  {
    id,
    bounds: node.bounds,
    depth: node.depth,
    octant: node.octant,
    object_count: node.count,
    child_mask: node.child_mask,
  }
}

///|
pub fn StaticObjectIndex::node_info(
  self : StaticObjectIndex,
  id : Int32,
) -> NodeInfo raise QueryError {
  guard id >= 0 && id < self.nodes.length() else { raise InvalidNode }
  info(id, self.nodes[id])
}

///|
/// Read one ID belonging directly to a node, including objects kept at parents.
pub fn StaticObjectIndex::node_object_id(
  self : StaticObjectIndex,
  id : Int32,
  offset : Int32,
) -> UInt32 raise QueryError {
  guard id >= 0 && id < self.nodes.length() else { raise InvalidNode }
  let node = self.nodes[id]
  guard offset >= 0 && offset < node.count else { raise InvalidObjectOffset }
  self.entries[node.start + offset].id
}

///|
/// Preorder traversal in ascending octant order. Return false to stop.
/// The callback may query the index, but must not reuse this active workspace.
pub fn StaticObjectIndex::visit(
  self : StaticObjectIndex,
  workspace : Workspace,
  visitor : (NodeInfo) -> Bool,
) -> Unit raise QueryError {
  guard workspace.capacity() >= self.summary.workspace_required else {
    raise InsufficientWorkspace(
      required=self.summary.workspace_required,
      available=workspace.capacity(),
    )
  }
  workspace.stack[0] = 0
  let mut top = 1
  while top > 0 {
    top -= 1
    let id = workspace.stack[top]
    let node = self.nodes[id]
    if !visitor(info(id, node)) {
      return
    }
    for i = node.child_count - 1; i >= 0; i = i - 1 {
      workspace.stack[top] = node.first_child + i
      top += 1
    }
  }
}