// Walker contract:
// - mutation descent copies path arrays before prepare hooks mutate them
// - leaf actions read LeafContext and return pure Splice descriptions
// - propagation rewrites only the unpublished candidate path and handles splits

///|
priv struct PathFrame[T] {
  children : Array[BTreeNode[T]]
  counts : Array[Int]
  child_idx : Int
}

///|
/// A cursor pointing at a leaf together with its ancestor path.
priv struct Cursor[T] {
  path : Array[PathFrame[T]]
  leaf_elem : T
  leaf_span : Int
  offset : Int
  child_idx : Int
}

///|
priv struct LeafCursor[T] {
  path : Array[PathFrame[T]]
  elem : T
  span : Int
  offset : Int
}

///|
/// LCA interval for range operations. child_height is used by range delete
/// to reconstruct boundary subtrees at the correct level.
priv struct AncestorRange[T] {
  prefix : Array[PathFrame[T]]
  children : Array[BTreeNode[T]]
  counts : Array[Int]
  start_idx : Int
  end_idx : Int
  child_height : Int
}

///|
priv struct NodeSplice[T] {
  prefix : Array[PathFrame[T]]
  children : Array[BTreeNode[T]]
  counts : Array[Int]
  start_idx : Int
  end_idx : Int
  new_children : Array[BTreeNode[T]]
  leaf_delta : Int
}

///|
/// Value snapshot passed to a leaf splice callback. It captures the current
/// leaf and optional adjacent values from the same immediate parent without
/// exposing a live tree collection. These values are not guaranteed logical
/// neighbors across parent boundaries. See the README's API Contracts section.
pub(all) struct LeafContext[T] {
  /// Current leaf value.
  elem : T
  /// Current leaf span.
  span : Int
  /// Span offset addressed by the mutation.
  offset : Int
  /// Current leaf index in its parent child array.
  child_idx : Int
  priv left_elem : T?
  priv right_elem : T?
} derive(Debug)

///|
/// Replacement description for a current leaf parent's child array.
/// `start_idx` and `end_idx` must satisfy
/// `0 <= start_idx <= end_idx <= parent child count`; invalid indices are not
/// separately validated.
pub(all) struct Splice[T] {
  /// Inclusive child index where replacement begins.
  start_idx : Int
  /// Exclusive child index where replacement ends.
  end_idx : Int
  /// Replacement leaves, inserted in order. Every span must be positive.
  new_leaves : Array[(T, Int)]
} derive(Debug)

///|
priv struct PropagateResult[T] {
  segment : Array[BTreeNode[T]]
  leaf_delta : Int
}

///|
/// Return the left sibling leaf value captured from the immediate parent, if
/// present. This is not a cross-parent logical predecessor lookup.
pub fn[T] LeafContext::left_neighbor(self : LeafContext[T]) -> T? {
  self.left_elem
}

///|
/// Return the right sibling leaf value captured from the immediate parent, if
/// present. This is not a cross-parent logical successor lookup.
pub fn[T] LeafContext::right_neighbor(self : LeafContext[T]) -> T? {
  self.right_elem
}

///|
fn[T] LeafContext::from_cursor(cursor : Cursor[T]) -> LeafContext[T] {
  guard cursor.path.length() > 0 else {
    abort("LeafContext::from_cursor: cursor missing parent frame")
  }
  let frame = cursor.path[cursor.path.length() - 1]
  let left_elem : T? = match frame.children.get(cursor.child_idx - 1) {
    Some(Leaf(elem~, ..)) => Some(elem)
    _ => None
  }
  let right_elem : T? = match frame.children.get(cursor.child_idx + 1) {
    Some(Leaf(elem~, ..)) => Some(elem)
    _ => None
  }
  {
    elem: cursor.leaf_elem,
    span: cursor.leaf_span,
    offset: cursor.offset,
    child_idx: cursor.child_idx,
    left_elem,
    right_elem,
  }
}

///|
fn[T] LeafCursor::from_cursor(cursor : Cursor[T]) -> LeafCursor[T] {
  {
    path: cursor.path,
    elem: cursor.leaf_elem,
    span: cursor.leaf_span,
    offset: cursor.offset,
  }
}