Skip to main content

tiny_trie/
nibble_trie.rs

1//! Nibble Trie — a fixed-fanout radix trie indexed by nibbles (half-bytes).
2//!
3//! Each node has 16 child slots (one per nibble value 0–15), addressed by
4//! direct indexing rather than binary search or SIMD. This trades space for
5//! simplicity and lookup speed: no comparison loops, no branch misprediction
6//! on the child search path.
7//!
8//! # Terminal Nodes
9//!
10//! Keys that are prefixes of other keys (e.g. "ab" in {"ab", "abc"}) are
11//! represented by a `terminal` flag on the node where the key ends, rather
12//! than a null-byte leaf child. This eliminates null terminators, allows
13//! `0x00` bytes in keys, and makes `get()` accept plain `&[u8]`.
14//!
15//! # Empty-slot encoding (`OptNz`)
16//!
17//! Child slots and the `leaf` field use `OptNz<PTR>` — a `#[repr(transparent)]`
18//! newtype over `PTR` where the value `0` means "empty" and any nonzero value
19//! is a real arena index or key index. `[OptNz<PTR>; 16]` is layout-identical
20//! to `[PTR; 16]`, so the SIMD `children_mask` path is reused via a single
21//! `repr(transparent)` pointer cast. Real arena child addresses are `>= 1`
22//! (the root at arena[0] is never a child target) and real key indices are
23//! `>= 1` (index[0] is a dummy entry), so `0` is free as the sentinel.
24//!
25//! # Key Index Encoding
26//!
27//! Real keys start at index 1 (index 0 is the dummy entry pointing at buf[0],
28//! an unused byte). `values[i]` corresponds to `index[i+1]` (i.e. key index
29//! `ki` maps to `values[ki - 1]`).
30
31use crate::ByteKey;
32use crate::tiny_array::TinyArray;
33use std::{fmt, marker::PhantomData, num::NonZero, ops::{Bound, RangeBounds}, simd::{Simd, cmp::SimdPartialEq}};
34
35/// One slot of the sparse `index`: the buf offset (>= 1; buf[0] is the dummy byte),
36/// the key length, and the value inline. `None` slots are gaps.
37pub type Slot<LEN, T> = (NonZero<usize>, LEN, T);
38
39// ---------------------------------------------------------------------------
40// TrieIndex trait
41// ---------------------------------------------------------------------------
42
43/// Trait for types used as arena/key indices and prefix lengths in NibbleTrie.
44///
45/// Implemented for `u8`, `u16`, `u32`, and `u64`. The type parameter `PTR` (pointer
46/// type) controls the width of `children`, `leaf`, and arena indices. The type
47/// parameter `LEN` (length type) controls the width of `prefix_len` and key
48/// lengths in the index.
49pub trait TrieIndex: Copy + Clone + Default + PartialEq + Eq + fmt::Debug + 'static {
50    /// Convert to `usize` for indexing.
51    fn as_usize(self) -> usize;
52    /// Maximum representable value (e.g. `u16::MAX` for u16).
53    fn max_value() -> usize;
54    /// Zero value, used for initial values and as the `OptNz` empty sentinel.
55    fn zero() -> Self;
56    /// Maximum value used as sentinel for empty slots in `children[]` by the
57    /// sibling tries (`fixed_len_nibble_trie`, `nib_trie`). `nibble_trie`
58    /// itself uses `0` as its sentinel (see `OptNz`), but keeps this method so
59    /// the trait stays shared.
60    fn max_value_sentinel() -> Self;
61    /// Convert from `usize`. May panic or truncate on overflow in debug builds.
62    fn from_usize(n: usize) -> Self;
63    /// Compute a 16-bit occupancy mask from a 16-slot children array.
64    /// Bit N is set if `children[N]` is not zero.
65    fn children_mask(children: &[Self; 16]) -> u16;
66}
67
68impl TrieIndex for u8 {
69    #[inline] fn as_usize(self) -> usize { self as usize }
70    #[inline] fn max_value() -> usize { u8::MAX as usize }
71    #[inline] fn zero() -> Self { 0 }
72    #[inline] fn max_value_sentinel() -> Self { u8::MAX }
73    #[inline] fn from_usize(n: usize) -> Self {
74        debug_assert!(n <= u8::MAX as usize, "u8 overflow: {n}");
75        n as u8
76    }
77    #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
78        crate::simd::children_mask_u8(children)
79    }
80}
81
82impl TrieIndex for u16 {
83    #[inline] fn as_usize(self) -> usize { self as usize }
84    #[inline] fn max_value() -> usize { u16::MAX as usize }
85    #[inline] fn zero() -> Self { 0 }
86    #[inline] fn max_value_sentinel() -> Self { u16::MAX }
87    #[inline] fn from_usize(n: usize) -> Self {
88        debug_assert!(n <= u16::MAX as usize, "u16 overflow: {n}");
89        n as u16
90    }
91    #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
92        crate::simd::children_mask_u16(children)
93    }
94}
95
96impl TrieIndex for u32 {
97    #[inline] fn as_usize(self) -> usize { self as usize }
98    #[inline] fn max_value() -> usize { u32::MAX as usize }
99    #[inline] fn zero() -> Self { 0 }
100    #[inline] fn max_value_sentinel() -> Self { u32::MAX }
101    #[inline] fn from_usize(n: usize) -> Self {
102        debug_assert!(n <= u32::MAX as usize, "u32 overflow: {n}");
103        n as u32
104    }
105    #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
106        crate::simd::children_mask(children)
107    }
108}
109
110impl TrieIndex for u64 {
111    #[inline] fn as_usize(self) -> usize { self as usize }
112    #[inline] fn max_value() -> usize { u64::MAX as usize }
113    #[inline] fn zero() -> Self { 0 }
114    #[inline] fn max_value_sentinel() -> Self { u64::MAX }
115    #[inline] fn from_usize(n: usize) -> Self { n as u64 }
116    #[inline] fn children_mask(children: &[Self; 16]) -> u16 {
117        crate::simd::children_mask_u64(children)
118    }
119}
120
121// ---------------------------------------------------------------------------
122// OptNz: 0-encoded optional index (no tag byte, layout-identical to PTR)
123// ---------------------------------------------------------------------------
124
125/// A nonzero-style optional index: a `#[repr(transparent)]` wrapper over `PTR`
126/// where the value `0` denotes "empty" and any nonzero value is a real index.
127///
128/// `OptNz<PTR>` has the same size and layout as `PTR`, so `[OptNz<PTR>; 16]` is
129/// layout-identical to `[PTR; 16]` (used to feed the SIMD `children_mask`). This
130/// is the stable, no-`unsafe`-on-access equivalent of `Option<NonZero<PTR>>`.
131#[repr(transparent)]
132#[derive(Copy, Clone, PartialEq, Eq)]
133pub(crate) struct OptNz<PTR: TrieIndex>(PTR);
134
135impl<PTR: TrieIndex> OptNz<PTR> {
136    /// The empty value (encodes `0`).
137    #[inline]
138    pub(crate) fn empty() -> Self { Self(PTR::zero()) }
139
140    /// Build from a raw `PTR`. Returns `None` if `v` is zero.
141    #[allow(dead_code)]
142    #[inline]
143    pub(crate) fn new(v: PTR) -> Option<Self> {
144        if v == PTR::zero() { None } else { Some(Self(v)) }
145    }
146
147    /// Build from a known-nonzero `PTR`. Debug-asserts `v != 0`.
148    #[inline]
149    pub(crate) fn from_index(v: PTR) -> Self {
150        debug_assert!(v != PTR::zero(), "OptNz::from_index: zero value");
151        Self(v)
152    }
153
154    /// The raw underlying `PTR` (zero if empty).
155    #[inline]
156    pub(crate) fn get(self) -> PTR { self.0 }
157
158    /// Whether this slot holds a real index.
159    #[inline]
160    pub(crate) fn is_some(self) -> bool { self.0 != PTR::zero() }
161
162    /// Whether this slot is empty.
163    #[inline]
164    pub(crate) fn is_none(self) -> bool { self.0 == PTR::zero() }
165}
166
167impl<PTR: TrieIndex> Default for OptNz<PTR> {
168    fn default() -> Self { Self(PTR::zero()) }
169}
170
171impl<PTR: TrieIndex> fmt::Debug for OptNz<PTR> {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        if self.is_none() { write!(f, "-") } else { write!(f, "{:?}", self.0) }
174    }
175}
176
177// ---------------------------------------------------------------------------
178// Core types
179// ---------------------------------------------------------------------------
180
181/// A single node in the nibble trie arena.
182///
183/// Generic over `PTR` (pointer/index type for children and arena references)
184/// and `LEN` (length type for prefix lengths and key lengths).
185///
186/// Layout with PTR=u32, LEN=u16: 76 bytes (64 children + 2 prefix_len + 2
187/// leaf_mask + 4 leaf + 1 terminal + 3 padding).
188/// With PTR=u16, LEN=u16: 40 bytes (32 children + 2 + 2 + 2 + 1 + 1 padding).
189#[derive(Copy, Clone)]
190pub(crate) struct Node<PTR: TrieIndex, LEN: TrieIndex> {
191    pub(crate) children: [OptNz<PTR>; 16],  // 0 = empty; leaf key index or arena index otherwise
192    pub(crate) prefix_len: LEN,             // absolute nibble position of the discriminating nibble
193    pub(crate) leaf_mask: u16,              // bit N set → children[N] is a leaf key index
194    pub(crate) leaf: OptNz<PTR>,            // key index of a reference/descendant leaf (for retrieval)
195    pub(crate) terminal: bool,              // true → this node's key ends here (prefix key)
196}
197
198impl<PTR: TrieIndex, LEN: TrieIndex> Node<PTR, LEN> {
199    pub(crate) fn new() -> Self {
200        Node {
201            children: [OptNz::empty(); 16],
202            prefix_len: LEN::zero(),
203            leaf_mask: 0,
204            leaf: OptNz::empty(),
205            terminal: false,
206        }
207    }
208
209    /// Whether this node is terminal (its own key ends here).
210    #[inline]
211    pub(crate) fn is_terminal(&self) -> bool {
212        self.terminal
213    }
214
215    /// Set the terminal flag.
216    #[inline]
217    fn set_terminal(&mut self, val: bool) {
218        self.terminal = val;
219    }
220
221    /// Check if nibble slot `nib` is a leaf (key index).
222    #[inline]
223    pub(crate) fn is_leaf(&self, nib: usize) -> bool {
224        debug_assert!(nib < 16);
225        (self.leaf_mask >> nib) & 1 == 1
226    }
227
228    /// Set the leaf flag for nibble slot `nib`.
229    #[inline]
230    fn set_leaf(&mut self, nib: usize) {
231        debug_assert!(nib < 16);
232        self.leaf_mask |= 1 << nib;
233    }
234
235    /// Clear the leaf flag for nibble slot `nib`.
236    #[inline]
237    fn clear_leaf(&mut self, nib: usize) {
238        debug_assert!(nib < 16);
239        self.leaf_mask &= !(1 << nib);
240    }
241
242    /// Check if nibble slot `nib` is occupied (holds a child, leaf or internal).
243    #[inline]
244    pub(crate) fn is_occupied(&self, nib: usize) -> bool {
245        debug_assert!(nib < 16);
246        self.children[nib].is_some()
247    }
248
249    /// Store a leaf key index at `nib`. Key index must be nonzero.
250    #[inline]
251    fn set_leaf_child(&mut self, nib: usize, key_index: PTR) {
252        debug_assert!(nib < 16);
253        debug_assert!(key_index != PTR::zero(), "zero key index");
254        self.set_leaf(nib);
255        self.children[nib] = OptNz::from_index(key_index);
256    }
257
258    /// Store an arena index at `nib` (internal node reference). Must be nonzero.
259    #[inline]
260    fn set_internal_child(&mut self, nib: usize, arena_idx: PTR) {
261        debug_assert!(nib < 16);
262        debug_assert!(arena_idx != PTR::zero(), "zero arena index");
263        self.clear_leaf(nib);
264        self.children[nib] = OptNz::from_index(arena_idx);
265    }
266
267    /// Decode a leaf child at `nib` into a key index.
268    /// Returns `None` if the slot is empty or not a leaf.
269    #[inline]
270    fn leaf_key_index(&self, nib: usize) -> Option<PTR> {
271        debug_assert!(nib < 16);
272        if self.is_leaf(nib) && self.children[nib].is_some() {
273            Some(self.children[nib].get())
274        } else {
275            None
276        }
277    }
278
279    /// Compute a 16-bit mask where bit N is set if `children[N]` is occupied.
280    /// Reuses the SIMD `children_mask` over the raw `[PTR; 16]` view — sound
281    /// because `OptNz<PTR>` is `#[repr(transparent)]` over `PTR`.
282    #[inline]
283    pub(crate) fn children_mask(&self) -> u16 {
284        // SAFETY: OptNz<PTR> is #[repr(transparent)] over PTR, so
285        // [OptNz<PTR>; 16] has identical layout to [PTR; 16].
286        let raw: &[PTR; 16] = unsafe { &*(&self.children as *const [OptNz<PTR>; 16] as *const [PTR; 16]) };
287        PTR::children_mask(raw)
288    }
289
290    /// Promote this node's PTR type to a wider one.
291    /// Child arena indices and leaf key indices are widened via `NewPTR::from_usize`.
292    pub(crate) fn promote<NewPTR: TrieIndex>(self) -> Node<NewPTR, LEN> {
293        let mut children = [OptNz::empty(); 16];
294        for i in 0..16 {
295            if self.children[i].is_some() {
296                children[i] = OptNz::from_index(NewPTR::from_usize(self.children[i].get().as_usize()));
297            }
298        }
299        Node {
300            children,
301            prefix_len: self.prefix_len,
302            leaf_mask: self.leaf_mask,
303            leaf: if self.leaf.is_some() {
304                OptNz::from_index(NewPTR::from_usize(self.leaf.get().as_usize()))
305            } else {
306                OptNz::empty()
307            },
308            terminal: self.terminal,
309        }
310    }
311
312    /// Demote this node's PTR type to a narrower one.
313    /// Returns `Err(self)` if any child index or leaf index doesn't fit
314    /// in the narrower type.
315    pub(crate) fn demote<NewPTR: TrieIndex>(self) -> Result<Node<NewPTR, LEN>, Self> {
316        for i in 0..16 {
317            if self.children[i].is_some() && self.children[i].get().as_usize() > NewPTR::max_value() {
318                return Err(self);
319            }
320        }
321        if self.leaf.is_some() && self.leaf.get().as_usize() > NewPTR::max_value() {
322            return Err(self);
323        }
324        let mut children = [OptNz::empty(); 16];
325        for i in 0..16 {
326            if self.children[i].is_some() {
327                children[i] = OptNz::from_index(NewPTR::from_usize(self.children[i].get().as_usize()));
328            }
329        }
330        Ok(Node {
331            children,
332            prefix_len: self.prefix_len,
333            leaf_mask: self.leaf_mask,
334            leaf: if self.leaf.is_some() {
335                OptNz::from_index(NewPTR::from_usize(self.leaf.get().as_usize()))
336            } else {
337                OptNz::empty()
338            },
339            terminal: self.terminal,
340        })
341    }
342}
343
344impl<PTR: TrieIndex, LEN: TrieIndex> fmt::Debug for Node<PTR, LEN> {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        let active: Vec<(usize, &str, PTR)> = (0..16)
347            .filter(|&n| self.children[n].is_some())
348            .map(|n| {
349                let tag = if self.is_leaf(n) { "L" } else { "I" };
350                (n, tag, self.children[n].get())
351            })
352            .collect();
353        f.debug_struct("Node")
354            .field("prefix_len", &self.prefix_len)
355            .field("leaf_mask", &format_args!("{:016b}", self.leaf_mask))
356            .field("terminal", &self.terminal)
357            .field("leaf", &self.leaf)
358            .field("children", &active)
359            .finish()
360    }
361}
362
363// ---------------------------------------------------------------------------
364// FlatNode (Fnode) — dense leaf-pack node (step 4: base + terminal + offset)
365// ---------------------------------------------------------------------------
366
367/// Maximum number of keys a [`FlatNode`] can hold: 1 reference key (`base`) +
368/// `FNODE_SLOTS` array slots.
369pub(crate) const FNODE_CAP: usize = 16;
370
371/// Number of array slots in a [`FlatNode`] (one less than [`FNODE_CAP`] — the
372/// leftmost/reference key is pulled out of the array into `base`).
373pub(crate) const FNODE_SLOTS: usize = 15;
374
375/// `offset` value meaning "branch marker" (no terminal key at this slot; its
376/// children follow as deeper array slots). Real offsets are `>= 1` because
377/// `base` is the smallest key index in the subtree.
378pub(crate) const FNODE_OFFSET_NULL: u8 = 0xFF;
379
380/// A dense leaf-pack node: collapses a small/deep subtree (≤ [`FNODE_CAP`]
381/// keys) into one node holding a flattened pre-order micro-trie.
382///
383/// **Encoding (step 4, revised):** `index` is kept in sorted key order (insert
384/// places each key at its sorted position), so a subtree's keys appear in
385/// `index` in increasing position order. The leftmost (reference) key's absolute
386/// `index` position is stored once as `base`; every other key is stored as a
387/// `u8` **offset** from `base` (`key_index = base + offset`). This collapses the
388/// 4 B ptr to 1 B and pays for the larger CAP. Keys still live in `buf`, pointed
389/// to by `index` — no change to key storage.
390///
391/// - `base` — the leftmost key's `index` position. Doubles as the reference key
392///   (same role as `Inode.leaf`) for `simd_check_prefix`. Its discriminating
393///   depth is `parent.prefix_len` (the parent already matched the edge nibble
394///   there), so it is **not stored** and **not an array slot**.
395/// - `terminal` — whether `base` (the subtree root) is itself terminal. `true` +
396///   deeper slots = terminal+branch root; `true` + no deeper slots = pure-leaf
397///   root; `false` = pure-branch root (`base` is reference-only). This lifts the
398///   step-3 "subtree root can't be terminal" restriction — the root gets its own
399///   representation outside the slot array.
400/// - `slots` — the non-leftmost keys, each `(prefix_len, offset)`: `prefix_len`
401///   is the discriminating depth (absolute nibble position where this key
402///   diverges from a sibling); `offset` is `key_index - base`. `offset ==
403///   [`FNODE_OFFSET_NULL`]` → pure branch marker (its children follow as deeper
404///   slots); otherwise a terminal key at `base + offset`. Because `base` is the
405///   smallest index in the subtree, real offsets are `>= 1`.
406///
407/// A `Some`-offset (≠ NULL) slot with deeper slots following is a
408/// **terminal+branch** node (a prefix key); `flat_get` descends past it when the
409/// query continues (`can_descend`) and lands on it (returning the terminal,
410/// verified by `simd_eq`) when the query is exhausted. A NULL-offset slot is a
411/// pure (non-terminal) branch. An Fnode is a DAG leaf — slots hold only key
412/// indices, never arena refs; multi-level structure is encoded via pre-order
413/// `prefix_len` (the flat scan algorithm).
414///
415/// `u8` offsets are safe because NibbleTrie is **insert-only** (no `remove`):
416/// `index` density stays 50–90% (`optimize`'s `2i+1` respread + the `>90%`
417/// trigger), so a ≤16-key subtree spans ≤~32 `index` slots → offsets ≤~32 ≪
418/// `0xFF`. No flatten guard needed now (if deletion is ever added: a `span ≤ 254`
419/// flatten-guard + split-on-overflow trigger).
420///
421/// `FlatNode` is `Copy`: every field (and every `TinyArray` element) is `Copy`,
422/// and `TinyArray` itself is `Copy` (no heap allocation, no `Drop`). So
423/// [`ArenaNode`] is `Copy` too — no borrow-not-copy constraint on the arena.
424#[derive(Copy, Clone, Debug)]
425pub(crate) struct FlatNode<PTR: TrieIndex, LEN: TrieIndex> {
426    pub(crate) nibbles: u64,                       // 15 nibbles × 4 bits (array slots 0..FNODE_SLOTS)
427    pub(crate) base: PTR,                          // index into `index` of the leftmost (reference) key
428    pub(crate) terminal: bool,                     // whether `base` (the subtree root) is itself terminal
429    pub(crate) slots: TinyArray<(LEN, u8), FNODE_SLOTS>, // (prefix_len, offset); offset 0xFF = branch marker
430}
431
432impl<PTR: TrieIndex, LEN: TrieIndex> FlatNode<PTR, LEN> {
433    pub(crate) fn new() -> Self {
434        FlatNode {
435            nibbles: 0,
436            base: PTR::zero(),
437            terminal: false,
438            slots: TinyArray::new(),
439        }
440    }
441
442    /// The `index` position of the key at array slot `i` (`base + offset`), or
443    /// `None` if slot `i` is a branch marker (offset == [`FNODE_OFFSET_NULL`]).
444    #[allow(dead_code)]
445    #[inline]
446    pub(crate) fn slot_key_index(&self, i: usize) -> Option<PTR> {
447        let (_plen, offset) = self.slots.as_slice()[i];
448        if offset == FNODE_OFFSET_NULL {
449            None
450        } else {
451            Some(PTR::from_usize(self.base.as_usize() + offset as usize))
452        }
453    }
454
455    /// The nibble stored at array slot `i`.
456    #[inline]
457    pub(crate) fn slot_nibble(&self, i: usize) -> u8 {
458        ((self.nibbles >> (4 * i)) & 0xF) as u8
459    }
460
461    /// The key index at [`Frame::Fnode`] position `pos`: `0` = `base`, `i+1` =
462    /// array slot `i`. Returns `None` if `pos` is not a terminal — `base` when
463    /// `!terminal`, or an array branch-marker slot (the latter never occurs: the
464    /// iterator only ever positions on terminals). Pre-order (base, then array
465    /// slots in nibble order) is sorted key order, so `pos` enumerates terminals
466    /// in ascending key order.
467    #[inline]
468    pub(crate) fn pos_key_index(&self, pos: usize) -> Option<PTR> {
469        if pos == 0 {
470            if self.terminal { Some(self.base) } else { None }
471        } else {
472            let i = pos - 1;
473            let (_plen, offset) = self.slots.as_slice()[i];
474            if offset == FNODE_OFFSET_NULL {
475                None
476            } else {
477                Some(PTR::from_usize(self.base.as_usize() + offset as usize))
478            }
479        }
480    }
481
482    /// First terminal position: `0` if `terminal`, else the first array slot
483    /// with a non-NULL offset (encoded as `slot+1`). `None` if no terminals.
484    #[inline]
485    pub(crate) fn first_terminal_pos(&self) -> Option<usize> {
486        if self.terminal {
487            Some(0)
488        } else {
489            self.next_terminal_pos(0)
490        }
491    }
492
493    /// Next terminal position strictly after `pos`: scans array slots from
494    /// `pos` for the next non-NULL offset (returned as `slot+1`). `pos==0`
495    /// (after `base`) starts at array slot 0; `pos==i+1` (after array slot `i`)
496    /// starts at array slot `i+1`. `None` if exhausted (caller pops the frame).
497    #[inline]
498    pub(crate) fn next_terminal_pos(&self, pos: usize) -> Option<usize> {
499        let slots = self.slots.as_slice();
500        for i in pos..slots.len() {
501            let (_plen, offset) = slots[i];
502            if offset != FNODE_OFFSET_NULL {
503                return Some(i + 1);
504            }
505        }
506        None
507    }
508
509    /// Number of terminal keys this Fnode represents: `base` (if `terminal`) plus
510    /// every array slot with a non-NULL offset. When `terminal=false`, `base` is
511    /// itself an array slot (offset 0), so it is counted by the loop; when `true`,
512    /// `base` is pulled out of the array and counted here.
513    pub(crate) fn key_count(&self) -> usize {
514        let mut n = if self.terminal { 1 } else { 0 };
515        for (_, offset) in self.slots.as_slice() {
516            if *offset != FNODE_OFFSET_NULL {
517                n += 1;
518            }
519        }
520        n
521    }
522
523    /// Promote the reference key index type to a wider `PTR` (only `base`
524    /// carries a `PTR`; the array slots are `(LEN, u8)` offsets).
525    fn promote<NewPTR: TrieIndex>(self) -> FlatNode<NewPTR, LEN> {
526        FlatNode {
527            nibbles: self.nibbles,
528            base: NewPTR::from_usize(self.base.as_usize()),
529            terminal: self.terminal,
530            slots: self.slots,
531        }
532    }
533
534    /// Demote the reference key index type to a narrower `PTR`. Returns
535    /// `Err(self)` if `base` doesn't fit in the narrower type. (Array-slot
536    /// offsets are `u8`, so they always fit.)
537    fn demote<NewPTR: TrieIndex>(self) -> Result<FlatNode<NewPTR, LEN>, Self> {
538        if self.base.as_usize() > NewPTR::max_value() {
539            return Err(self);
540        }
541        Ok(FlatNode {
542            nibbles: self.nibbles,
543            base: NewPTR::from_usize(self.base.as_usize()),
544            terminal: self.terminal,
545            slots: self.slots,
546        })
547    }
548}
549
550impl<PTR: TrieIndex, LEN: TrieIndex> Default for FlatNode<PTR, LEN> {
551    fn default() -> Self { Self::new() }
552}
553
554/// Tagged arena element: `Inode` (the existing 16-slot direct-addressed
555/// [`Node`]) or `Fnode` (a [`FlatNode`]). `Copy` — both variants are `Copy`
556/// (`FlatNode` is `Copy` since `TinyArray` is), so arena reads may copy freely.
557#[derive(Copy, Clone, Debug)]
558pub(crate) enum ArenaNode<PTR: TrieIndex, LEN: TrieIndex> {
559    Inode(Node<PTR, LEN>),
560    Fnode(FlatNode<PTR, LEN>),
561}
562
563impl<PTR: TrieIndex, LEN: TrieIndex> ArenaNode<PTR, LEN> {
564    /// Promote the arena index type to a wider `PTR` (dispatches by variant).
565    fn promote<NewPTR: TrieIndex>(self) -> ArenaNode<NewPTR, LEN> {
566        match self {
567            ArenaNode::Inode(n) => ArenaNode::Inode(n.promote()),
568            ArenaNode::Fnode(f) => ArenaNode::Fnode(f.promote()),
569        }
570    }
571
572    /// Demote the arena index type to a narrower `PTR` (dispatches by variant).
573    /// Returns `Err(self)` if any index doesn't fit.
574    fn demote<NewPTR: TrieIndex>(self) -> Result<ArenaNode<NewPTR, LEN>, Self> {
575        match self {
576            ArenaNode::Inode(n) => n.demote().map(ArenaNode::Inode).map_err(ArenaNode::Inode),
577            ArenaNode::Fnode(f) => f.demote().map(ArenaNode::Fnode).map_err(ArenaNode::Fnode),
578        }
579    }
580}
581
582// ---------------------------------------------------------------------------
583// NibbleTrie
584// ---------------------------------------------------------------------------
585
586#[derive(Clone)]
587pub struct NibbleTrie<K, T, PTR: TrieIndex = u32, LEN: TrieIndex = u16>
588where
589    K: ByteKey,
590{
591    pub(crate) arena: Vec<ArenaNode<PTR, LEN>>,
592    pub(crate) buf: Vec<u8>,                // all keys concatenated (no null terminators)
593    pub(crate) index: Vec<Option<Slot<LEN, T>>>, // sparse: position == key index; None = gap; [0] = dummy
594    pub(crate) n_keys: usize,               // live key count (replaces index.len()-1)
595    _key: PhantomData<K>,
596}
597
598// ---------------------------------------------------------------------------
599// Divergence result
600// ---------------------------------------------------------------------------
601
602/// Outcome of comparing two keys for divergence starting from a given nibble
603/// position. `from` lets callers skip already-confirmed-matching prefixes.
604enum DivergeResult {
605    /// The keys are identical (same nibble count, same content).
606    Duplicate,
607    /// The keys diverge at this nibble position, or one key is a prefix of the
608    /// other (position = length of the shorter key in nibbles).
609    At(usize),
610}
611
612/// Outcome of a bounded prefix check: scan nibbles `from..to` and report
613/// whether the keys match in that range or diverge at a specific nibble.
614/// Unlike `DivergeResult`, this does not scan past `to` and has no
615/// `Duplicate` variant — a full match within the bound is `Matches`.
616enum PrefixCheck {
617    /// The keys match at every nibble position in `from..to`.
618    Matches,
619    /// The keys diverge at this nibble position (within `from..to`).
620    Diverges(usize),
621}
622
623/// Scan two keys from `from` onward to find the first diverging nibble.
624#[inline]
625fn find_divergence(key_a: &[u8], key_b: &[u8], from: usize) -> DivergeResult {
626    let total_a = nibble_count(key_a);
627    let total_b = nibble_count(key_b);
628    let min = total_a.min(total_b);
629    let mut d = from;
630    while d < min {
631        if key_nibble_at(key_a, d) != key_nibble_at(key_b, d) {
632            return DivergeResult::At(d);
633        }
634        d += 1;
635    }
636    if total_a == total_b {
637        DivergeResult::Duplicate
638    } else {
639        DivergeResult::At(d)
640    }
641}
642
643/// Given two differing bytes, return the nibble index of the first divergence.
644/// High nibble (bits 7–4) is checked first; if they match, the low nibble
645/// (bits 3–0) diverges. Branchless: XOR → check if high nibble is zero → add 1.
646#[inline]
647fn diverging_nibble(xor: u8, byte_idx: usize) -> usize {
648    byte_idx * 2 + ((xor >> 4 == 0) as usize)
649}
650
651/// SIMD-accelerated byte equality check. Returns `true` if both slices have
652/// the same length and identical content. Uses 16-byte lanes for the bulk
653/// of the comparison, with a scalar tail for the remainder.
654#[inline]
655fn simd_eq(a: &[u8], b: &[u8]) -> bool {
656    if a.len() != b.len() {
657        return false;
658    }
659    let len = a.len();
660    let mut i = 0;
661    while i + 16 <= len {
662        let va = Simd::<u8, 16>::from_slice(unsafe { a.get_unchecked(i..i + 16) });
663        let vb = Simd::<u8, 16>::from_slice(unsafe { b.get_unchecked(i..i + 16) });
664        if va.simd_ne(vb).any() {
665            return false;
666        }
667        i += 16;
668    }
669    // Scalar tail
670    while i < len {
671        if unsafe { *a.get_unchecked(i) != *b.get_unchecked(i) } {
672            return false;
673        }
674        i += 1;
675    }
676    true
677}
678
679fn simd_find_divergence<const N: usize>(key_a: &[u8], key_b: &[u8], from: usize) -> DivergeResult
680{
681    let minlen = key_a.len().min(key_b.len());
682    let mut i = from / 2; // byte containing nibble `from`
683
684    while i + N <= minlen {
685        let a = Simd::<u8, N>::from_slice(unsafe { key_a.get_unchecked(i..i + N) });
686        let b = Simd::<u8, N>::from_slice(unsafe { key_b.get_unchecked(i..i + N) });
687        let mask = a.simd_ne(b);
688        if mask.any() {
689            let diff_byte_idx = i + mask.first_set().unwrap();
690            let xor = unsafe { *key_a.get_unchecked(diff_byte_idx) ^ *key_b.get_unchecked(diff_byte_idx) };
691            return DivergeResult::At(diverging_nibble(xor, diff_byte_idx));
692        }
693        i += N;
694    }
695
696    // Scalar tail
697    find_divergence(key_a, key_b, i * 2)
698}
699
700/// Scan nibbles `from..to` of two keys. Returns `Diverges(pos)` if they differ
701/// at any nibble in that range, or `Matches` if they agree throughout.
702/// An empty range (`from >= to`) is trivially `Matches`.
703#[inline]
704fn check_prefix(key_a: &[u8], key_b: &[u8], from: usize, to: usize) -> PrefixCheck {
705    for nib in from..to {
706        if key_nibble_at(key_a, nib) != key_nibble_at(key_b, nib) {
707            return PrefixCheck::Diverges(nib);
708        }
709    }
710    PrefixCheck::Matches
711}
712
713/// SIMD-accelerated bounded prefix check. Scans nibbles `from..to` and stops
714/// at the first divergence within that range. Returns `Matches` if the keys
715/// agree throughout, or `Diverges(pos)` at the first differing nibble.
716fn simd_check_prefix<const N: usize>(key_a: &[u8], key_b: &[u8], from: usize, to: usize) -> PrefixCheck
717{
718    if from >= to {
719        return PrefixCheck::Matches;
720    }
721
722    let from_byte = from / 2;
723    let to_byte = (to + 1) / 2; // first byte fully outside the nibble range
724    let minlen = key_a.len().min(key_b.len()).min(to_byte);
725    let mut i = from_byte;
726
727    while i + N <= minlen {
728        let a = Simd::<u8, N>::from_slice(unsafe { key_a.get_unchecked(i..i + N) });
729        let b = Simd::<u8, N>::from_slice(unsafe { key_b.get_unchecked(i..i + N) });
730        let mask = a.simd_ne(b);
731        if mask.any() {
732            let diff_byte_idx = i + mask.first_set().unwrap();
733            let xor = unsafe { *key_a.get_unchecked(diff_byte_idx) ^ *key_b.get_unchecked(diff_byte_idx) };
734            let nib = diverging_nibble(xor, diff_byte_idx);
735            if nib < to {
736                return PrefixCheck::Diverges(nib);
737            }
738            // Divergence past the bound — keys match within range
739            return PrefixCheck::Matches;
740        }
741        i += N;
742    }
743
744    // Scalar tail
745    check_prefix(key_a, key_b, i * 2, to)
746}
747
748// ---------------------------------------------------------------------------
749// Nibble helpers
750// ---------------------------------------------------------------------------
751
752#[inline]
753fn key_nibble_at(key: &[u8], idx: usize) -> u8 {
754    let byte_idx = idx / 2;
755    if byte_idx < key.len() {
756        if idx % 2 == 0 {
757            key[byte_idx] >> 4
758        } else {
759            key[byte_idx] & 0x0F
760        }
761    } else {
762        0
763    }
764}
765
766/// Unchecked version of `key_nibble_at`.
767///
768/// # Safety
769/// `idx / 2` must be < `key.len()` (i.e., the nibble index must be in bounds).
770#[allow(dead_code)]
771#[inline]
772unsafe fn key_nibble_at_unchecked(key: &[u8], idx: usize) -> u8 {
773    let byte_idx = idx / 2;
774    debug_assert!(byte_idx < key.len(), "nibble {idx} out of bounds for key len {}", key.len());
775    if idx % 2 == 0 {
776        unsafe { *key.get_unchecked(byte_idx) >> 4 }
777    } else {
778        unsafe { *key.get_unchecked(byte_idx) & 0x0F }
779    }
780}
781
782#[inline]
783fn nibble_count(key: &[u8]) -> usize {
784    key.len() * 2
785}
786
787// ---------------------------------------------------------------------------
788// NibbleTrie methods
789// ---------------------------------------------------------------------------
790
791impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
792    /// Return the key slice for `key_index`.
793    #[inline]
794    fn key_slice(&self, key_index: PTR) -> &[u8] {
795        let (off, len, _) = self.index[key_index.as_usize()].as_ref().unwrap();
796        &self.buf[off.get()..off.get() + len.as_usize()]
797    }
798
799    /// Borrow the `Inode` at arena index `i`.
800    ///
801    /// This is the single chokepoint for the **Inode-only** code paths (insert,
802    /// bump_walk, optimize, the invariant oracle) that do not yet handle Fnodes.
803    /// The read path (`get`/`get_unchecked`) and `NibbleIter` dispatch Fnodes
804    /// separately (`flat_get` / `Frame::Fnode`) and never call this on an Fnode.
805    /// Panics if `arena[i]` is an `Fnode` — a sign an Inode-only path reached one
806    /// (step 4/5 wire those paths).
807    #[inline]
808    fn inode(&self, i: usize) -> &Node<PTR, LEN> {
809        match &self.arena[i] {
810            ArenaNode::Inode(n) => n,
811            ArenaNode::Fnode(_) => panic!("inode(): arena[{i}] is an Fnode (Inode-only path)"),
812        }
813    }
814
815    /// Mutably borrow the `Inode` at arena index `i`. See [`inode`](Self::inode).
816    #[inline]
817    fn inode_mut(&mut self, i: usize) -> &mut Node<PTR, LEN> {
818        match &mut self.arena[i] {
819            ArenaNode::Inode(n) => n,
820            ArenaNode::Fnode(_) => panic!("inode_mut(): arena[{i}] is an Fnode (Inode-only path)"),
821        }
822    }
823
824    pub fn new() -> Self {
825        NibbleTrie {
826            arena: Vec::new(),
827            buf: vec![0],           // buf[0] = dummy (unused byte)
828            index: vec![None],      // index[0] = dummy gap
829            n_keys: 0,
830            _key: PhantomData,
831        }
832    }
833
834    pub fn len(&self) -> usize {
835        self.n_keys
836    }
837
838    pub fn is_empty(&self) -> bool {
839        self.n_keys == 0
840    }
841
842    // -----------------------------------------------------------------------
843    // Lookup
844    // -----------------------------------------------------------------------
845
846    /// Flat scan over a [`FlatNode`] (Fnode): a pre-order DFS of a
847    /// path-compressed micro-trie. The Fnode collapses the subtree **rooted at
848    /// `base`** (the leftmost key), so every array slot is a *descendant* of
849    /// `base` and diverges at a depth `> parent.prefix_len` (= `P`, the depth
850    /// the parent Inode already matched when it dispatched here). `base`'s own
851    /// discriminating depth is `P` — already consumed — so it is not stored and
852    /// not an array slot; the scan walks only the array slots (all depth `> P`)
853    /// and falls back to `base` when no array slot is reachable.
854    ///
855    /// Each array slot `i` is `(prefix_len, offset)`: `prefix_len` is the
856    /// *discriminating depth* (absolute nibble position where this key diverges
857    /// from a sibling); `offset` is `key_index - base` (`0xFF` = branch marker,
858    /// no terminal; otherwise a terminal key index at `base + offset`).
859    ///
860    /// A non-NULL offset with deeper slots following is a **terminal+branch**
861    /// node (a prefix key). The scan handles terminals and branches uniformly:
862    /// on a nibble match, descend into the entry's subtree iff the next entry is
863    /// strictly deeper *and* the query hasn't exhausted (`can_descend`);
864    /// otherwise *land* on this slot — return the terminal (verified by full-key
865    /// `simd_eq`, since path compression means bytes between discriminant depths
866    /// were never compared) for a non-NULL offset, or `None` for a branch
867    /// marker. A terminal+branch slot is **descended past** when the query
868    /// continues (the longer key lives below) and **landed on** when the query
869    /// is exhausted (the prefix key itself).
870    ///
871    /// When the scan exhausts / surfaces above the frontier without landing on
872    /// an array slot, the query's path leads to `base`: return `base` iff
873    /// `terminal` and `simd_eq(base_key, query)`. (`base` at depth `P` is the
874    /// only terminal not encoded as an array slot; the parent already matched
875    /// its nibble at `P`, so it is the implicit landing point for a query that
876    /// equals `base` or is a prefix of all array keys.)
877    ///
878    /// Algorithm mirrors `notes/fnode.md` §"Step 4 design (REVISED)" / §"flat
879    /// scan algorithm": descend following the query key's nibbles; on a nibble
880    /// mismatch advance — entries in a subtree we haven't descended into
881    /// (`d > depth`) are skipped by the depth guard.
882    fn flat_get(&self, node: &FlatNode<PTR, LEN>, key: &[u8]) -> Option<usize> {
883        let slots = node.slots.as_slice();
884        let max_nib = key.len() * 2;
885        // Scan the array slots (all depth > P). Skip the scan entirely if the
886        // shallowest array slot is already past the query's length — then the
887        // query can only land on `base`.
888        if !slots.is_empty() {
889            let mut depth = slots[0].0.as_usize(); // shallowest array-slot depth
890            if depth < max_nib {
891                let mut i = 0;
892                while i < slots.len() {
893                    let d = slots[i].0.as_usize();
894                    if d < depth {
895                        // Surfaced above the current frontier — no further match.
896                        break;
897                    }
898                    if d > depth {
899                        // In a subtree we haven't descended into — skip.
900                        i += 1;
901                        continue;
902                    }
903                    let nib = node.slot_nibble(i);
904                    if key_nibble_at(key, d) != nib {
905                        i += 1;
906                        continue;
907                    }
908                    // On path. Can the query descend further into this entry's subtree?
909                    let can_descend = i + 1 < slots.len()
910                        && slots[i + 1].0.as_usize() > d
911                        && slots[i + 1].0.as_usize() < max_nib;
912                    if can_descend {
913                        depth = slots[i + 1].0.as_usize();
914                        i += 1;
915                    } else {
916                        // Landed on array slot i — the query can't go deeper.
917                        // The offset tells terminal-ness; a non-NULL offset is
918                        // verified by full-key equality (path compression).
919                        let offset = slots[i].1;
920                        return if offset != FNODE_OFFSET_NULL {
921                            let ki = node.base.as_usize() + offset as usize;
922                            if simd_eq(self.key_slice(PTR::from_usize(ki)), key) {
923                                Some(ki)
924                            } else {
925                                None
926                            }
927                        } else {
928                            None // pure branch marker
929                        };
930                    }
931                }
932            }
933        }
934        // No array slot matched at the query's remaining depth → land on `base`.
935        // Return it iff `terminal` and its full key equals the query.
936        if node.terminal {
937            let ki = node.base;
938            if simd_eq(self.key_slice(ki), key) {
939                return Some(ki.as_usize());
940            }
941        }
942        None
943    }
944
945    pub fn get_index(&self, key: &[u8]) -> Option<usize> {
946        if self.arena.is_empty() {
947            return None;
948        }
949        let mut phys_idx: usize = 0;
950        let max_nib = key.len() * 2;
951        loop {
952            let node = self.inode(phys_idx);
953            let prefix_len = node.prefix_len.as_usize();
954            // Key nibbles exhausted — check if this node is terminal.
955            if prefix_len >= max_nib {
956                if node.is_terminal() {
957                    let ki = node.leaf.get();
958                    let (off, len, _) = self.index[ki.as_usize()].as_ref().unwrap();
959                    let off = off.get();
960                    let key_in_buf = &self.buf[off..off + len.as_usize()];
961                    if key.len() == len.as_usize() && simd_eq(&key_in_buf[..key.len()], key) {
962                        return Some(ki.as_usize());
963                    }
964                }
965                return None;
966            }
967            let nib = key_nibble_at(key, prefix_len) as usize;
968            if !node.is_occupied(nib) {
969                return None;
970            }
971            if node.is_leaf(nib) {
972                let key_index = node.children[nib].get();
973                return if simd_eq(self.key_slice(key_index), key) {
974                    Some(key_index.as_usize())
975                } else {
976                    None
977                };
978            }
979            // Internal child — Inode or Fnode.
980            let child = node.children[nib].get().as_usize();
981            match &self.arena[child] {
982                ArenaNode::Inode(_) => phys_idx = child,
983                ArenaNode::Fnode(f) => return self.flat_get(f, key),
984            }
985        }
986    }
987
988    /// Unchecked lookup — assumes the key is present in the trie.
989    ///
990    /// # Safety
991    /// The key **must** have been inserted into this trie. All child/leaf indices
992    /// encountered during traversal must be valid arena or index entries.
993    #[cfg(feature = "unchecked")]
994    unsafe fn get_index_unchecked(&self, key: &[u8]) -> Option<usize> {
995        if self.arena.is_empty() {
996            return None;
997        }
998        let mut phys_idx: usize = 0;
999        let max_nib = key.len() * 2;
1000        loop {
1001            // SAFETY: phys_idx is the root (always an Inode by invariant) or an
1002            // Inode child arena index. Fnode children are dispatched below
1003            // before re-looping, so this read is always an Inode.
1004            let node = match unsafe { self.arena.get_unchecked(phys_idx) } {
1005                ArenaNode::Inode(n) => n,
1006                ArenaNode::Fnode(_) => panic!("get_unchecked: phys_idx {phys_idx} is an Fnode (dispatcher missed it)"),
1007            };
1008            let prefix_len = node.prefix_len.as_usize();
1009            if prefix_len >= max_nib {
1010                debug_assert!(node.is_terminal(), "get_unchecked: key not in set");
1011                return Some(node.leaf.get().as_usize());
1012            }
1013            let nib = unsafe { key_nibble_at_unchecked(key, prefix_len) } as usize;
1014            let slot = unsafe { node.children.get_unchecked(nib) };
1015            if slot.is_none() {
1016                return None;
1017            }
1018            if node.is_leaf(nib) {
1019                return Some(slot.get().as_usize());
1020            }
1021            let child = slot.get().as_usize();
1022            // Internal child — Inode or Fnode.
1023            // SAFETY: `child` is a valid arena index read from an Inode.
1024            match unsafe { self.arena.get_unchecked(child) } {
1025                ArenaNode::Inode(_) => phys_idx = child,
1026                ArenaNode::Fnode(f) => return self.flat_get(f, key),
1027            }
1028        }
1029    }
1030
1031    pub fn get(&self, key: &[u8]) -> Option<&T> {
1032        self.get_index(key).map(|idx| &self.index[idx].as_ref().unwrap().2)
1033    }
1034
1035    pub fn get_mut(&mut self, key: &[u8]) -> Option<&mut T> {
1036        self.get_index(key).map(|idx| &mut self.index[idx].as_mut().unwrap().2)
1037    }
1038
1039    ///if the key is guaranteed to be in the set, the final comparison can be skipped, improving perf substantially.
1040    #[cfg(feature = "unchecked")]
1041    pub unsafe fn get_unchecked(&self, key: &[u8]) -> Option<&T> {
1042        unsafe {self.get_index_unchecked(key).map(|idx| &self.index[idx].as_ref().unwrap().2) }
1043    }
1044
1045    // -----------------------------------------------------------------------
1046    // Iteration
1047    // -----------------------------------------------------------------------
1048
1049    /// An internal tree-walking cursor, used to position the public `Cursor`
1050    /// (via `seek`) and by `bump_walk` (via `seek` + `stack`).
1051    pub(crate) fn walk_iter(&self) -> NibbleIter<'_, K, T, PTR, LEN> {
1052        NibbleIter::new(self)
1053    }
1054
1055    /// Public forward cursor: parked *before* the first key (so `current()` is
1056    /// `None` and `next()` yields the first key). A linear scan over the sparse
1057    /// `index`, skipping `None` gaps.
1058    pub fn iter(&self) -> Cursor<'_, K, T, PTR, LEN> {
1059        Cursor::new(self)
1060    }
1061
1062    /// Public reverse cursor: parked *on* the last key (`current()` returns it,
1063    /// `prev()` walks backward). Linear scan over `index`.
1064    pub fn iter_last(&self) -> Cursor<'_, K, T, PTR, LEN> {
1065        Cursor::new_last(self)
1066    }
1067
1068    /// Public forward mutable cursor: parked *before* the first key, lending out
1069    /// `&mut T` borrows tied to the cursor (see [`CursorMut`]).
1070    pub fn iter_mut(&mut self) -> CursorMut<'_, K, T, PTR, LEN> {
1071        CursorMut::new(self)
1072    }
1073
1074    /// Public reverse mutable cursor: parked *on* the last key, lending out
1075    /// `&mut T` borrows tied to the cursor (see [`CursorMut`]).
1076    pub fn iter_mut_last(&mut self) -> CursorMut<'_, K, T, PTR, LEN> {
1077        CursorMut::new_last(self)
1078    }
1079
1080    /// Iterate the keys in `bounds` in ascending order — a zero-allocation
1081    /// [`Range`] yielding `(K::Borrowed<'_>, &T)`. Both bounds are resolved by
1082    /// O(keylen) seeks up front; the scan between them is then bounded by slot
1083    /// index (`pos < end_pos`), so no per-element key comparison is needed.
1084    /// Accepts any [`RangeBounds<&[u8]>`]: `start..end`, `start..`, `..end`,
1085    /// `..` (operands are `&[u8]`). The bounds' byte slices are used only during
1086    /// the initial seeks and need not outlive the call.
1087    pub fn range<'q>(&self, bounds: impl RangeBounds<&'q [u8]>) -> Range<'_, K, T, PTR, LEN> {
1088        // `RangeBounds<&'q [u8]>::start_bound` returns `Bound<&'q &'q [u8]>`-ish;
1089        // deref the inner reference to get `Bound<&'q [u8]>` for `Range::new`.
1090        let start = bounds.start_bound().map(|b| *b);
1091        let end = bounds.end_bound().map(|b| *b);
1092        Range::new(self, start, end)
1093    }
1094
1095    /// Like [`range`](Self::range) but with explicit [`Bound`]s — for mixed
1096    /// `Included`/`Excluded` bounds without the `&&[u8]` double-reference the
1097    /// `RangeBounds` tuple form would require.
1098    pub fn range_bounds(
1099        &self,
1100        start: Bound<&[u8]>,
1101        end: Bound<&[u8]>,
1102    ) -> Range<'_, K, T, PTR, LEN> {
1103        Range::new(self, start, end)
1104    }
1105
1106    pub fn into_keys_values(self) -> (Vec<K>, Vec<T>) {
1107        let buf = self.buf;
1108        let mut keys: Vec<K> = Vec::with_capacity(self.n_keys);
1109        let mut values: Vec<T> = Vec::with_capacity(self.n_keys);
1110        for (i, slot) in self.index.into_iter().enumerate() {
1111            if i == 0 { continue; } // dummy
1112            if let Some((off, len, val)) = slot {
1113                keys.push(K::from_bytes(&buf[off.get()..off.get() + len.as_usize()]));
1114                values.push(val);
1115            }
1116        }
1117        (keys, values)
1118    }
1119
1120    // -----------------------------------------------------------------------
1121    // Capacity
1122    // -----------------------------------------------------------------------
1123
1124    pub fn near_capacity(&self) -> bool {
1125        // Arena child addresses and key indices are nonzero and must fit in PTR.
1126        self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value()
1127    }
1128
1129    // -----------------------------------------------------------------------
1130    // Optimize (DFS key-sorted buf rewrite + sparse 2*i+1 index re-spread)
1131    // -----------------------------------------------------------------------
1132
1133    /// Rewrite `buf` in DFS (key-sorted) order and re-spread `index` into a
1134    /// sparse layout: a fresh vec of capacity `2*n+1` with each key placed at
1135    /// slot `2*i+1` (DFS rank `i`), leaving even slots as `None` gaps. Forward
1136    /// iteration then hits `buf` in ascending memory order, and the gaps give
1137    /// future inserts room to shift into without re-sorting.
1138    ///
1139    /// Also (re)establishes the leftmost-`leaf` invariant: every node's `leaf`
1140    /// is set to the key index of the leftmost key in its subtree. The arena
1141    /// topology (child structure) is unchanged — only key indices are remapped.
1142    /// Idempotent.
1143    pub fn optimize(&mut self) {
1144        if self.arena.is_empty() {
1145            return;
1146        }
1147
1148        let n = self.n_keys;
1149        let cap = 2 * n + 1;
1150        // Build a gap-filled vec without requiring T: Clone (vec![None; cap] would).
1151        let mut new_index: Vec<Option<Slot<LEN, T>>> = (0..cap).map(|_| None).collect();
1152        let mut new_buf: Vec<u8> = Vec::with_capacity(self.buf.len());
1153        new_buf.push(0); // dummy byte at position 0
1154        let mut cursor: usize = 1;
1155        let mut i: usize = 0; // global DFS rank
1156
1157        self.walk_optimize(0, &mut new_index, &mut new_buf, &mut cursor, &mut i);
1158
1159        new_buf.truncate(cursor);
1160        self.buf = new_buf;
1161        self.index = new_index;
1162        // NOTE: `flatten()` is NOT called here yet. Wiring it in makes `optimize`
1163        // produce Fnodes, but `insert` (step 5) is still Inode-only and panics on
1164        // an Fnode — so insert-after-optimize would break until the
1165        // `fnode_mode::OptimizeOnly` expand-on-write path lands. `walk_optimize`
1166        // already remaps Fnode `base`+offsets, so a *standalone* `flatten` →
1167        // `optimize` → `flatten` cycle is correct; call `flatten()` explicitly.
1168    }
1169
1170    /// DFS walk that places each key at `2*i+1` in `new_index`, copies its bytes
1171    /// contiguously into `new_buf`, rewrites the arena's key-index references
1172    /// (`children[nib]` for leaf children, `leaf` for the node's leftmost,
1173    /// `base`+offsets for Fnode children), and returns the slot of the leftmost
1174    /// key placed in this subtree.
1175    fn walk_optimize(
1176        &mut self,
1177        phys_idx: usize,
1178        new_index: &mut Vec<Option<Slot<LEN, T>>>,
1179        new_buf: &mut Vec<u8>,
1180        cursor: &mut usize,
1181        i: &mut usize,
1182    ) -> usize {
1183        // `Node: Copy`, so copy the inner Inode out to avoid borrow conflicts
1184        // while we recurse (which needs `&mut self`) and rewrite arena slots.
1185        let node = *self.inode(phys_idx);
1186        let mut first: Option<usize> = None;
1187
1188        // This node's own terminal key sorts before all its descendants.
1189        if node.is_terminal() {
1190            let slot = self.place_key(node.leaf.get().as_usize(), new_index, new_buf, cursor, i);
1191            first = Some(slot);
1192        }
1193
1194        // Visit children in nibble order (== sorted order); leaf children become
1195        // keys, internal children (Inode or Fnode) are recursed into / remapped.
1196        for nib in 0..16 {
1197            if !node.is_occupied(nib) {
1198                continue;
1199            }
1200            let child_phys = node.children[nib].get().as_usize();
1201            if node.is_leaf(nib) {
1202                let slot = self.place_key(child_phys, new_index, new_buf, cursor, i);
1203                self.inode_mut(phys_idx).children[nib] = OptNz::from_index(PTR::from_usize(slot));
1204                if first.is_none() {
1205                    first = Some(slot);
1206                }
1207            } else {
1208                let child_first = match self.arena[child_phys] {
1209                    ArenaNode::Inode(_) => {
1210                        self.walk_optimize(child_phys, new_index, new_buf, cursor, i)
1211                    }
1212                    // An Fnode child: place its keys in pre-order (base then
1213                    // array terminal slots) and remap `base`+offsets to the new
1214                    // `2i+1` slots. Branch markers place no key (offset stays
1215                    // `0xFF`). The Fnode is a DAG leaf — no arena refs to fix.
1216                    ArenaNode::Fnode(f) => {
1217                        let old_base = f.base.as_usize();
1218                        let new_base = self.place_key(old_base, new_index, new_buf, cursor, i);
1219                        let mut new_slots: TinyArray<(LEN, u8), FNODE_SLOTS> = TinyArray::new();
1220                        for (plen, offset) in f.slots.as_slice() {
1221                            if *offset == FNODE_OFFSET_NULL {
1222                                // Branch marker — no key to place.
1223                                new_slots.push((*plen, FNODE_OFFSET_NULL));
1224                            } else if *offset == 0 {
1225                                // The base key itself (terminal=false case: `base`
1226                                // is an array slot at offset 0). Already placed
1227                                // above as `new_base`; keep it at offset 0.
1228                                new_slots.push((*plen, 0));
1229                            } else {
1230                                let old_ki = old_base + *offset as usize;
1231                                let new_slot = self.place_key(old_ki, new_index, new_buf, cursor, i);
1232                                new_slots.push((*plen, (new_slot - new_base) as u8));
1233                            }
1234                        }
1235                        self.arena[child_phys] = ArenaNode::Fnode(FlatNode {
1236                            nibbles: f.nibbles,
1237                            base: PTR::from_usize(new_base),
1238                            terminal: f.terminal,
1239                            slots: new_slots,
1240                        });
1241                        new_base
1242                    }
1243                };
1244                if first.is_none() {
1245                    first = Some(child_first);
1246                }
1247            }
1248        }
1249
1250        let leftmost = first.expect("walk_optimize: node must have at least one key in subtree");
1251        self.inode_mut(phys_idx).leaf = OptNz::from_index(PTR::from_usize(leftmost));
1252        leftmost
1253    }
1254
1255    /// Place the key currently at `old_ki` into `new_index`/`new_buf` at slot
1256    /// `2*i+1` (advancing `i`), copy its bytes contiguously, and return the new
1257    /// slot. Takes the old slot out of `index` (so the value moves, no `T:
1258    /// Clone`). Shared by the Inode terminal/leaf-child paths and the Fnode
1259    /// base/slot paths of [`walk_optimize`](Self::walk_optimize).
1260    fn place_key(
1261        &mut self,
1262        old_ki: usize,
1263        new_index: &mut Vec<Option<Slot<LEN, T>>>,
1264        new_buf: &mut Vec<u8>,
1265        cursor: &mut usize,
1266        i: &mut usize,
1267    ) -> usize {
1268        let slot = 2 * *i + 1;
1269        *i += 1;
1270        let (off, len, val) = self.index[old_ki].take().unwrap();
1271        let old_off = off.get();
1272        let start = *cursor;
1273        new_buf.resize(start + len.as_usize(), 0);
1274        new_buf[start..start + len.as_usize()]
1275            .copy_from_slice(&self.buf[old_off..old_off + len.as_usize()]);
1276        *cursor = start + len.as_usize();
1277        new_index[slot] = Some((NonZero::new(start).unwrap(), len, val));
1278        slot
1279    }
1280
1281    /// Flatten small multi-Inode subtrees into single [`FlatNode`]s.
1282    ///
1283    /// Rebuilds the arena top-down: any non-root subtree with ≤ [`FNODE_CAP`]
1284    /// keys and ≥ 2 Inodes (so collapsing it actually saves memory) is replaced
1285    /// by one Fnode built from a pre-order DFS of that subtree. The root stays
1286    /// an Inode. This is an **arena-only** rebuild — key indices (leaf children,
1287    /// each Inode's `leaf`, and Fnode `base`/offsets) are unchanged; only
1288    /// internal-child arena indices are remapped. So `index`/`buf` are untouched
1289    /// and the existing `get`/`iter`/`seek` paths work unchanged.
1290    ///
1291    /// Idempotent: an Fnode holds no arena refs, so a subtree already containing
1292    /// an Fnode can't be re-flattened (the Fnode is copied verbatim and
1293    /// [`build_fnode_subtree`] rejects Fnode children). Calling `flatten` on an
1294    /// already-flat trie is a no-op topology copy.
1295    ///
1296    /// Best called after [`optimize`](Self::optimize): the `2i+1` respread
1297    /// makes a ≤16-key subtree span exactly ≤32 `index` slots, so every offset
1298    /// fits in `u8` with room (≤ 30 ≪ `0xFF`), and offsets come out canonical
1299    /// even values `0, 2, 4, …`.
1300    pub fn flatten(&mut self) {
1301        if self.arena.is_empty() {
1302            return;
1303        }
1304        // Pass 1: count keys & Inodes per subtree (bottom-up), keyed by old phys.
1305        // `(0, 0)` is the "not yet counted" sentinel (every real subtree has ≥1
1306        // key and ≥1 Inode).
1307        let mut counts: Vec<(usize, usize)> = vec![(0, 0); self.arena.len()];
1308        self.count_subtree(0, &mut counts);
1309        // Pass 2: rebuild the arena top-down, flattening qualifying subtrees.
1310        let mut new_arena: Vec<ArenaNode<PTR, LEN>> = Vec::with_capacity(self.arena.len());
1311        self.rebuild_subtree(0, &mut new_arena, &counts);
1312        self.arena = new_arena;
1313    }
1314
1315    /// Bottom-up subtree key/Inode counts. Fills `counts[phys] = (n_keys,
1316    /// n_inodes)` for `phys` and every descendant. Fnodes are DAG leaves (no
1317    /// arena children): they contribute their [`FlatNode::key_count`] and 1
1318    /// Inode-equivalent.
1319    fn count_subtree(&self, phys: usize, counts: &mut [(usize, usize)]) {
1320        if counts[phys] != (0, 0) {
1321            return; // already counted (defensive — a tree, so reached once)
1322        }
1323        let (keys, inodes) = match &self.arena[phys] {
1324            ArenaNode::Fnode(f) => (f.key_count(), 1),
1325            ArenaNode::Inode(node) => {
1326                let mut k = if node.is_terminal() { 1 } else { 0 };
1327                let mut i = 1;
1328                for nib in 0..16 {
1329                    if !node.is_occupied(nib) {
1330                        continue;
1331                    }
1332                    if node.is_leaf(nib) {
1333                        k += 1;
1334                    } else {
1335                        let child = node.children[nib].get().as_usize();
1336                        self.count_subtree(child, counts);
1337                        let (ck, ci) = counts[child];
1338                        k += ck;
1339                        i += ci;
1340                    }
1341                }
1342                (k, i)
1343            }
1344        };
1345        counts[phys] = (keys, inodes);
1346    }
1347
1348    /// Rebuild the subtree rooted at old `phys` into `new_arena`, returning its
1349    /// new arena index. Flattens qualifying non-root subtrees into one Fnode
1350    /// (consuming their old child Inodes — no orphans); otherwise copies the
1351    /// Inode and recurses, remapping internal-child arena indices. Fnode
1352    /// children are copied verbatim (no arena refs to remap). Leaf children and
1353    /// `leaf` are key indices, unchanged by this arena-only rebuild.
1354    fn rebuild_subtree(
1355        &self,
1356        phys: usize,
1357        new_arena: &mut Vec<ArenaNode<PTR, LEN>>,
1358        counts: &[(usize, usize)],
1359    ) -> usize {
1360        if let ArenaNode::Fnode(f) = &self.arena[phys] {
1361            new_arena.push(ArenaNode::Fnode(*f));
1362            return new_arena.len() - 1;
1363        }
1364        let node = *self.inode(phys);
1365        let (n_keys, n_inodes) = counts[phys];
1366        // Flatten qualifying subtrees. The root (phys == 0) is always kept an
1367        // Inode. `build_fnode_subtree` may still reject (Fnode child / offset
1368        // overflow); then fall through to copy + recurse.
1369        if phys != 0 && n_keys <= FNODE_CAP && n_inodes >= 2 {
1370            if let Some(fnode) = self.build_fnode_subtree(phys) {
1371                new_arena.push(ArenaNode::Fnode(fnode));
1372                return new_arena.len() - 1;
1373            }
1374        }
1375        // Copy the Inode (with old internal-child indices) and remap its internal
1376        // children. Leaf-child slots and `leaf` carry key indices — unchanged.
1377        let new_phys = new_arena.len();
1378        new_arena.push(ArenaNode::Inode(node));
1379        for nib in 0..16 {
1380            if node.is_occupied(nib) && !node.is_leaf(nib) {
1381                let child_old = node.children[nib].get().as_usize();
1382                let child_new = self.rebuild_subtree(child_old, new_arena, counts);
1383                match &mut new_arena[new_phys] {
1384                    ArenaNode::Inode(n) => {
1385                        n.children[nib] = OptNz::from_index(PTR::from_usize(child_new))
1386                    }
1387                    _ => unreachable!("rebuild_subtree: placeholder was Inode"),
1388                }
1389            }
1390        }
1391        new_phys
1392    }
1393
1394    /// Build a [`FlatNode`] from the Inode subtree rooted at `phys`: pre-order
1395    /// DFS collecting `(prefix_len, key_index)` per array slot, with `base` =
1396    /// the root's `leaf` (leftmost key) and `terminal` = the root's own
1397    /// terminal flag. Returns `None` if `phys` is not an Inode, the subtree
1398    /// contains an Fnode child (merging Fnodes is not supported), the slot count
1399    /// would exceed [`FNODE_SLOTS`], or an offset would collide with the `0xFF`
1400    /// sentinel. Does NOT check `phys != 0` — the root-stays-Inode invariant is
1401    /// the caller's responsibility.
1402    fn build_fnode_subtree(&self, phys: usize) -> Option<FlatNode<PTR, LEN>> {
1403        if !matches!(self.arena[phys], ArenaNode::Inode(_)) {
1404            return None;
1405        }
1406        let root = *self.inode(phys);
1407        let base = root.leaf.get();
1408        let terminal = root.is_terminal();
1409        let mut plens: Vec<LEN> = Vec::new();
1410        let mut key_idxs: Vec<Option<PTR>> = Vec::new();
1411        let mut nibbles: u64 = 0;
1412        let mut ok = true;
1413        self.collect_flat_slots(phys, &mut plens, &mut key_idxs, &mut nibbles, &mut ok);
1414        if !ok || plens.is_empty() || plens.len() > FNODE_SLOTS {
1415            return None;
1416        }
1417        let base_u = base.as_usize();
1418        let mut slots: TinyArray<(LEN, u8), FNODE_SLOTS> = TinyArray::new();
1419        for (plen, kidx) in plens.into_iter().zip(key_idxs.into_iter()) {
1420            let offset = match kidx {
1421                None => FNODE_OFFSET_NULL,
1422                Some(ki) => {
1423                    let off = ki.as_usize() - base_u;
1424                    if off >= FNODE_OFFSET_NULL as usize {
1425                        return None;
1426                    }
1427                    off as u8
1428                }
1429            };
1430            slots.push((plen, offset));
1431        }
1432        Some(FlatNode { nibbles, base, terminal, slots })
1433    }
1434
1435    /// Pre-order DFS collecting the subtree at `phys` into `plens`/`key_idxs`/
1436    /// `nibbles` (per-slot `(prefix_len, Option<key_index>)`). Sets `ok = false`
1437    /// and returns early on: an Fnode child (can't merge), or the slot count
1438    /// exceeding [`FNODE_SLOTS`]. A terminal+branch internal child emits its
1439    /// own key (`Some`) as the edge slot, then its descendants; a pure branch
1440    /// emits `None`. For a terminal subtree root, the root's own key is pulled
1441    /// out into `base`/`terminal` (by the caller) and NOT emitted here.
1442    fn collect_flat_slots(
1443        &self,
1444        phys: usize,
1445        plens: &mut Vec<LEN>,
1446        key_idxs: &mut Vec<Option<PTR>>,
1447        nibbles: &mut u64,
1448        ok: &mut bool,
1449    ) {
1450        let node = *self.inode(phys);
1451        let p = node.prefix_len;
1452        for nib in 0..16 {
1453            if !node.is_occupied(nib) {
1454                continue;
1455            }
1456            let i = plens.len();
1457            if i >= FNODE_SLOTS {
1458                *ok = false;
1459                return;
1460            }
1461            *nibbles |= (nib as u64) << (4 * i);
1462            if node.is_leaf(nib) {
1463                plens.push(p);
1464                key_idxs.push(Some(node.children[nib].get()));
1465            } else {
1466                let child = node.children[nib].get().as_usize();
1467                if matches!(self.arena[child], ArenaNode::Fnode(_)) {
1468                    *ok = false;
1469                    return;
1470                }
1471                let child_node = *self.inode(child);
1472                let ptr = if child_node.is_terminal() {
1473                    Some(child_node.leaf.get())
1474                } else {
1475                    None
1476                };
1477                plens.push(p);
1478                key_idxs.push(ptr);
1479                self.collect_flat_slots(child, plens, key_idxs, nibbles, ok);
1480                if !*ok {
1481                    return;
1482                }
1483            }
1484        }
1485    }
1486}
1487
1488impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Default for NibbleTrie<K, T, PTR, LEN> {
1489    fn default() -> Self { Self::new() }
1490}
1491
1492// ---------------------------------------------------------------------------
1493// Insertion (Stage B: shift-based slot allocation + bump walk)
1494// ---------------------------------------------------------------------------
1495
1496/// The resolved insertion case, produced by a non-mutating descent
1497/// (`find_insert_case`) BEFORE any arena/index mutation. All sub-cases and
1498/// nibble values are read from the pre-mutation tree, so the case stays valid
1499/// across the slot shift + bump (which only remap key indices, never arena
1500/// topology or nibble positions). The one exception — `SplitLeaf`'s existing
1501/// key index — is re-read from the arena in `execute_case` (post-bump) rather
1502/// than captured here, because that leaf slot may be the successor `p` and get
1503/// bumped from `p` to `p+1`.
1504enum Case {
1505    /// New key is a prefix of the node's reference key → `phys` becomes terminal.
1506    Terminal { phys: usize },
1507    /// New key diverges at `phys.prefix_len` into an empty nibble slot → leaf child.
1508    NewLeafChild { phys: usize, nib: usize },
1509    /// New key diverges from the node's reference key mid-prefix → split `phys`
1510    /// into a new parent (at `diverge`) holding the new key and the old subtree.
1511    SplitNode {
1512        phys: usize,
1513        diverge: usize,
1514        new_is_terminal: bool,
1515        new_nib: usize,
1516        ref_nib: usize,
1517        new_is_leftmost: bool,
1518    },
1519    /// New key diverges from an existing leaf child of `phys` at nibble `nib`
1520    /// → replace that leaf with a new split node holding both keys.
1521    SplitLeaf {
1522        phys: usize,
1523        nib: usize,
1524        d: usize,
1525        new_is_terminal: bool,
1526        existing_is_terminal: bool,
1527        new_nib: usize,
1528        exist_nib: usize,
1529        new_is_leftmost: bool,
1530    },
1531}
1532
1533impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
1534    pub fn insert(&mut self, key: K, value: T) -> Result<usize, ()> {
1535        let key_bytes = key.bytes();
1536        // Overflow checks: arena/key indices must fit in PTR (nonzero, so < max).
1537        if self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value() {
1538            return Err(());
1539        }
1540        if key_bytes.len() * 2 > LEN::max_value() {
1541            return Err(());
1542        }
1543
1544        // 90% capacity trigger: when the sparse index or buf is nearly full,
1545        // re-spread into a fresh 2n+1 layout so future shifts have gaps to land in.
1546        // Skip the re-spread if `2n+1` would overflow PTR (`2n < max` ⟺ `2n+1 <= max`,
1547        // then the trie is simply near its index capacity; the overflow checks below
1548        // return Err instead).
1549        if self.needs_optimize() && 2 * self.n_keys < PTR::max_value() {
1550            self.optimize();
1551        }
1552        // Overflow checks: arena/key indices must fit in PTR (nonzero, so < max).
1553        if self.arena.len() >= PTR::max_value() || self.index.len() >= PTR::max_value() {
1554            return Err(());
1555        }
1556
1557        let key_len = LEN::from_usize(key_bytes.len());
1558        let off = self.buf.len();
1559        self.buf.extend_from_slice(key_bytes);
1560        // buf[0] is the dummy byte, so every real key offset is >= 1 → NonZero.
1561        self.n_keys += 1;
1562        let max_nib = key_bytes.len() * 2;
1563
1564        if self.arena.is_empty() {
1565            return Ok(self.insert_into_empty_trie(off, key_len, value, key_bytes, max_nib));
1566        }
1567
1568        // 1. Detect: non-mutating descent resolves the case + the descent path.
1569        let (case, path) = match self.find_insert_case(key_bytes, max_nib) {
1570            Ok(c) => c,
1571            Err(()) => {
1572                // Duplicate — no slot was pushed yet (slot alloc happens below),
1573                // so rollback just drops the buf extend and the key count.
1574                self.buf.truncate(off);
1575                self.n_keys -= 1;
1576                return Err(());
1577            }
1578        };
1579
1580        // 2. Compute p: the slot the successor key currently occupies (the new
1581        //    key sorts into position p, shifting [p, p+n-1] right). None = END
1582        //    (new key is the largest → append, no shift, no bump).
1583        let p_opt = self.compute_p(&case, &path);
1584        let (p, n) = match p_opt {
1585            None => {
1586                let p = self.index.len();
1587                self.index
1588                    .push(Some((NonZero::new(off).unwrap(), key_len, value)));
1589                self.execute_case(case, p, &path);
1590                return Ok(p);
1591            }
1592            Some(p) => {
1593                // Scan forward from p, counting occupied slots until the first gap.
1594                // (All keys from p onward are contiguous until a None gap — the
1595                // successor and its trailing run that must shift right by one.)
1596                let mut n = 0;
1597                while p + n < self.index.len() && self.index[p + n].is_some() {
1598                    n += 1;
1599                }
1600                (p, n)
1601            }
1602        };
1603
1604        // Ensure room for the shift: the trailing gap may lie past `index.len()`.
1605        if p + n >= self.index.len() {
1606            self.index.push(None);
1607        }
1608
1609        if n > 0 {
1610            // 3. Position a forward walk at the successor key (slot p) by seeking.
1611            //    The seek borrows self immutably; copy out the (all-Copy) stack
1612            //    and drop the borrow before mutating.
1613            let succ_bytes = {
1614                let (soff, slen, _) = self.index[p].as_ref().unwrap();
1615                self.buf[soff.get()..soff.get() + slen.as_usize()].to_vec()
1616            };
1617            let stack: Vec<(usize, u16, usize)> = {
1618                let mut it = self.walk_iter();
1619                it.seek(&succ_bytes);
1620                debug_assert_eq!(
1621                    it.current_index(),
1622                    Some(p),
1623                    "seek must land on the successor slot"
1624                );
1625                it.stack
1626                    .iter()
1627                    .map(|frame| match *frame {
1628                        Frame::Inode { encoded, mask, nib } => (encoded.as_usize(), mask, nib),
1629                        // Fnode frames can't reach bump_walk yet — inserts never
1630                        // touch an Fnode until step 5 wires flat_insert/split.
1631                        Frame::Fnode { .. } => panic!(
1632                            "bump_walk init: Fnode frame on stack — insert-into-Fnode is step 5"
1633                        ),
1634                    })
1635                    .collect()
1636            };
1637
1638            // 4. Bump arena refs whose key index ∈ [p, p+n-1] (every shifted key's
1639            //    structural ptr + every node whose leftmost is a shifted key).
1640            self.bump_walk(stack, p, n);
1641
1642            // 5. Shift the slots right by one. A `take()` walk from the right end
1643            //    (not `copy_within`, which needs `T: Copy`) — a true element-wise
1644            //    move that leaves `None` at `p` for the new slot.
1645            for i in (0..n).rev() {
1646                self.index[p + i + 1] = self.index[p + i].take();
1647            }
1648        }
1649
1650        // 6. Place the new key's slot at p.
1651        self.index[p] = Some((NonZero::new(off).unwrap(), key_len, value));
1652
1653        // 7. Wire the new key into the arena at slot p (re-reading any
1654        //    bump-sensitive leaf index from the arena post-bump), then propagate
1655        //    the leftmost-`leaf` invariant up the spine.
1656        self.execute_case(case, p, &path);
1657        Ok(p)
1658    }
1659
1660    /// 90% capacity trigger. Measures fill as `n_keys / index.capacity()` (NOT
1661    /// `len / capacity`): after `optimize`, `len == capacity` because the gaps
1662    /// are real `None` slots, so `len` would always read as 100% full.
1663    #[inline]
1664    fn needs_optimize(&self) -> bool {
1665        let idx_cap = self.index.capacity();
1666        let buf_cap = self.buf.capacity();
1667        (idx_cap > 0 && 10 * self.n_keys > 9 * idx_cap)
1668            || (buf_cap > 0 && 10 * self.buf.len() > 9 * buf_cap)
1669    }
1670
1671    /// Non-mutating descent mirroring the lookup walk, but it RECORDS the
1672    /// resolved `Case` and descent `path` instead of mutating. Reads the
1673    /// reference/existing keys here (before any shift moves their slots).
1674    /// Returns `Err(())` for duplicates.
1675    fn find_insert_case(
1676        &self,
1677        key: &[u8],
1678        max_nib: usize,
1679    ) -> Result<(Case, Vec<(usize, usize)>), ()> {
1680        let mut phys_idx: usize = 0;
1681        let mut confirmed: usize = 0;
1682        // Path of (ancestor_phys, nib_used_to_descend) from root to the current
1683        // node, used to propagate the leftmost-`leaf` invariant up the spine.
1684        let mut path: Vec<(usize, usize)> = Vec::new();
1685
1686        loop {
1687            let node = self.inode(phys_idx);
1688            let ki = node.leaf.get();
1689            let (off, ref_len, _) = self.index[ki.as_usize()].as_ref().unwrap();
1690            let off = off.get();
1691            let ref_key = &self.buf[off..off + ref_len.as_usize()];
1692            let prefix_len = node.prefix_len.as_usize();
1693
1694            match simd_check_prefix::<8>(key, ref_key, confirmed, prefix_len) {
1695                PrefixCheck::Diverges(diverge) => {
1696                    let new_nib = key_nibble_at(key, diverge) as usize;
1697                    let ref_nib = key_nibble_at(ref_key, diverge) as usize;
1698                    let new_is_terminal = diverge >= max_nib;
1699                    let new_is_leftmost = new_is_terminal || new_nib < ref_nib;
1700                    return Ok((
1701                        Case::SplitNode {
1702                            phys: phys_idx,
1703                            diverge,
1704                            new_is_terminal,
1705                            new_nib,
1706                            ref_nib,
1707                            new_is_leftmost,
1708                        },
1709                        path,
1710                    ));
1711                }
1712                PrefixCheck::Matches => {
1713                    if max_nib == prefix_len {
1714                        if key.len() == ref_key.len() {
1715                            return Err(()); // exact duplicate
1716                        }
1717                        // New key is a prefix of the ref key → node becomes terminal.
1718                        return Ok((Case::Terminal { phys: phys_idx }, path));
1719                    }
1720
1721                    confirmed = prefix_len + 1;
1722                    let nib = key_nibble_at(key, prefix_len) as usize;
1723                    if !node.is_occupied(nib) {
1724                        // Empty slot — new key diverges here as a leaf child.
1725                        return Ok((Case::NewLeafChild { phys: phys_idx, nib }, path));
1726                    }
1727
1728                    if node.is_leaf(nib) {
1729                        // Split the existing leaf child: resolve divergence here.
1730                        path.push((phys_idx, nib));
1731                        let existing_key_index = node.children[nib].get();
1732                        let (eo, elen, _) =
1733                            self.index[existing_key_index.as_usize()].as_ref().unwrap();
1734                        let existing_key = &self.buf[eo.get()..eo.get() + elen.as_usize()];
1735                        match simd_find_divergence::<8>(key, existing_key, confirmed) {
1736                            DivergeResult::Duplicate => return Err(()),
1737                            DivergeResult::At(d) => {
1738                                let new_is_terminal = d >= max_nib;
1739                                let existing_is_terminal = d >= existing_key.len() * 2;
1740                                let new_nib = key_nibble_at(key, d) as usize;
1741                                let exist_nib = key_nibble_at(existing_key, d) as usize;
1742                                let new_is_leftmost = if new_is_terminal {
1743                                    true
1744                                } else if existing_is_terminal {
1745                                    false
1746                                } else {
1747                                    new_nib < exist_nib
1748                                };
1749                                return Ok((
1750                                    Case::SplitLeaf {
1751                                        phys: phys_idx,
1752                                        nib,
1753                                        d,
1754                                        new_is_terminal,
1755                                        existing_is_terminal,
1756                                        new_nib,
1757                                        exist_nib,
1758                                        new_is_leftmost,
1759                                    },
1760                                    path,
1761                                ));
1762                            }
1763                        }
1764                    }
1765
1766                    path.push((phys_idx, nib));
1767                    phys_idx = node.children[nib].get().as_usize();
1768                }
1769            }
1770        }
1771    }
1772
1773    /// Compute `p`: the key index of the successor (the leftmost key that sorts
1774    /// STRICTLY AFTER the new key). The new key takes slot `p`, shifting the
1775    /// successor and its trailing run right. `None` means the new key is the
1776    /// largest (END — append, no shift). Reads only pre-mutation state.
1777    fn compute_p(&self, case: &Case, path: &[(usize, usize)]) -> Option<usize> {
1778        match case {
1779            Case::Terminal { phys } => Some(self.inode(*phys).leaf.get().as_usize()),
1780            Case::NewLeafChild { phys, nib } => self.right_anchor(*phys, *nib, path),
1781            Case::SplitNode {
1782                phys,
1783                new_is_terminal,
1784                new_nib,
1785                ref_nib,
1786                ..
1787            } => {
1788                if *new_is_terminal || *new_nib < *ref_nib {
1789                    // New key is the new leftmost of `phys`'s subtree → successor
1790                    // is the old leftmost (the ref key), read before mutation.
1791                    Some(self.inode(*phys).leaf.get().as_usize())
1792                } else {
1793                    self.subtree_successor(path)
1794                }
1795            }
1796            Case::SplitLeaf {
1797                phys,
1798                nib,
1799                new_is_terminal,
1800                existing_is_terminal,
1801                new_nib,
1802                exist_nib,
1803                ..
1804            } => {
1805                let existing_key_index = self.inode(*phys).children[*nib].get().as_usize();
1806                if *new_is_terminal {
1807                    Some(existing_key_index)
1808                } else if *existing_is_terminal {
1809                    self.right_anchor(*phys, *nib, path)
1810                } else if *new_nib < *exist_nib {
1811                    Some(existing_key_index)
1812                } else {
1813                    self.right_anchor(*phys, *nib, path)
1814                }
1815            }
1816        }
1817    }
1818
1819    /// The leftmost key index of the next-higher subtree at `phys` (relative to
1820    /// nib `nib`), i.e. the successor of a key ending at `phys` via `nib`. Falls
1821    /// back to `subtree_successor` if `phys` has no higher occupied nibble.
1822    /// Uses the leftmost-`leaf` invariant: an internal child's `leaf` is its
1823    /// subtree's leftmost key index.
1824    fn right_anchor(&self, phys: usize, nib: usize, path: &[(usize, usize)]) -> Option<usize> {
1825        let mask = self.inode(phys).children_mask();
1826        let higher = if nib >= 15 { 0u16 } else { mask & !((1u16 << (nib + 1)) - 1) };
1827        if higher != 0 {
1828            let next_nib = higher.trailing_zeros() as usize;
1829            let r = self.inode(phys).children[next_nib].get();
1830            Some(if self.inode(phys).is_leaf(next_nib) {
1831                r.as_usize()
1832            } else {
1833                self.inode(r.as_usize()).leaf.get().as_usize()
1834            })
1835        } else {
1836            self.subtree_successor(path)
1837        }
1838    }
1839
1840    /// Walk up `path` (deepest first); at each `(parent, nib)` find a higher
1841    /// occupied nibble than the one descended through. The leftmost of that
1842    /// higher subtree is the successor. `None` = no higher ancestor nibble =
1843    /// the new key is the largest (END).
1844    fn subtree_successor(&self, path: &[(usize, usize)]) -> Option<usize> {
1845        for &(parent, nib) in path.iter().rev() {
1846            let mask = self.inode(parent).children_mask();
1847            let higher = if nib >= 15 { 0u16 } else { mask & !((1u16 << (nib + 1)) - 1) };
1848            if higher != 0 {
1849                let next_nib = higher.trailing_zeros() as usize;
1850                let r = self.inode(parent).children[next_nib].get();
1851                return Some(if self.inode(parent).is_leaf(next_nib) {
1852                    r.as_usize()
1853                } else {
1854                    self.inode(r.as_usize()).leaf.get().as_usize()
1855                });
1856            }
1857        }
1858        None
1859    }
1860
1861    /// Bump every arena ref whose key index ∈ [lo, lo+n-1]: each shifted key's
1862    /// structural ptr (terminal → `node.leaf`, leaf child → `node.children[nib]`)
1863    /// and every node whose `leaf` (leftmost) is a shifted key.
1864    ///
1865    /// Done as a forward DFS walk from slot `lo` for exactly `n` keys, mirroring
1866    /// `NibbleIter`'s advance/push_next_child/descend_first but with direct
1867    /// `&mut self` arena mutation. Navigation stays safe mid-walk: internal
1868    /// `children[nib]` are arena indices (unchanged by a key-index shift); leaf
1869    /// `children[nib]` are terminal for navigation; `leaf` is never traversed.
1870    ///
1871    /// Bumping rule (unified, avoids double-bumping terminal nodes whose
1872    /// `leaf` IS their structural ptr): bump `leaf` of EVERY touched node whose
1873    /// `leaf ∈ [lo,hi]` (seek-path ancestors + nodes entered via descend_first),
1874    /// and bump `children[nib]` for each visited leaf-child key. Terminal keys'
1875    /// structural ptr is their node's `leaf`, bumped once by the first rule.
1876    fn bump_walk(&mut self, init_stack: Vec<(usize, u16, usize)>, lo: usize, n: usize) {
1877        debug_assert!(n >= 1);
1878        let hi = lo + n - 1; // inclusive
1879        let mut stack = init_stack;
1880
1881        // Bump `leaf` of every node on the initial (seek) stack if in range.
1882        // These are the ancestors of `lo` plus `lo`'s owning node.
1883        for &(phys, _mask, _nib) in &stack {
1884            let l = self.inode(phys).leaf.get().as_usize();
1885            if l >= lo && l <= hi {
1886                self.inode_mut(phys).leaf = OptNz::from_index(PTR::from_usize(l + 1));
1887            }
1888        }
1889
1890        // Walk forward exactly n keys, bumping each leaf-child structural ptr.
1891        let mut seen = 0;
1892        while seen < n {
1893            let &(phys, _mask, nib) = stack.last().expect("bump_walk: stack emptied early");
1894            if nib == TERMINAL_NIB {
1895                // Terminal key: its structural ptr is `arena[phys].leaf`, already
1896                // bumped above (when this frame was pushed — its leaf == this
1897                // key's index, which is in range).
1898                seen += 1;
1899            } else {
1900                let k = self.inode(phys).children[nib].get().as_usize();
1901                // k ∈ [lo,hi] by construction (we visit exactly the shifted run).
1902                self.inode_mut(phys).children[nib] = OptNz::from_index(PTR::from_usize(k + 1));
1903                seen += 1;
1904            }
1905            if seen == n {
1906                break;
1907            }
1908            if !self.bump_advance(&mut stack, lo, hi) {
1909                debug_assert!(seen >= n, "bump_walk: tree exhausted before n keys");
1910                break;
1911            }
1912        }
1913    }
1914
1915    /// `descend_first` with `leaf`-bumping: walk down the lowest-nib spine of
1916    /// the subtree at `phys`, pushing a frame per node and bumping each node's
1917    /// `leaf` if in range, until a terminal key or a leaf-child is current.
1918    fn bump_descend_first(
1919        &mut self,
1920        stack: &mut Vec<(usize, u16, usize)>,
1921        mut phys: usize,
1922        lo: usize,
1923        hi: usize,
1924    ) {
1925        loop {
1926            // `Node: Copy` — copy the inner Inode out so we can mutate the
1927            // arena slot (bumping `leaf`) and re-loop without borrow conflicts.
1928            let node = *self.inode(phys);
1929            let l = node.leaf.get().as_usize();
1930            if l >= lo && l <= hi {
1931                self.inode_mut(phys).leaf = OptNz::from_index(PTR::from_usize(l + 1));
1932            }
1933            if node.is_terminal() {
1934                let mask = node.children_mask();
1935                stack.push((phys, mask, TERMINAL_NIB));
1936                return;
1937            }
1938            let mask = node.children_mask();
1939            debug_assert!(mask != 0, "bump_descend_first: non-terminal node with no children");
1940            let nib = mask.trailing_zeros() as usize;
1941            stack.push((phys, mask, nib));
1942            if node.is_leaf(nib) {
1943                return;
1944            } else {
1945                phys = node.children[nib].get().as_usize();
1946            }
1947        }
1948    }
1949
1950    /// `push_next_child` with descent: find the next occupied nibble ≥
1951    /// `start_nib` at `encoded`, push its frame, and if it is an internal
1952    /// child, `bump_descend_first` into it. Returns false if no such nibble.
1953    #[inline]
1954    fn bump_push_next(
1955        &mut self,
1956        stack: &mut Vec<(usize, u16, usize)>,
1957        encoded: usize,
1958        mask: u16,
1959        start_nib: usize,
1960        lo: usize,
1961        hi: usize,
1962    ) -> bool {
1963        let shifted = if start_nib >= 16 { 0u16 } else { mask >> start_nib };
1964        if shifted == 0 {
1965            return false;
1966        }
1967        let nib = start_nib + shifted.trailing_zeros() as usize;
1968        debug_assert!(nib < 16);
1969        stack.push((encoded, mask, nib));
1970        if !self.inode(encoded).is_leaf(nib) {
1971            let addr = self.inode(encoded).children[nib].get().as_usize();
1972            self.bump_descend_first(stack, addr, lo, hi);
1973        }
1974        true
1975    }
1976
1977    /// `advance_next` with mutation: pop frames and `bump_push_next` from the
1978    /// next nibble until a key is current. Returns false if the stack empties.
1979    #[inline]
1980    fn bump_advance(
1981        &mut self,
1982        stack: &mut Vec<(usize, u16, usize)>,
1983        lo: usize,
1984        hi: usize,
1985    ) -> bool {
1986        loop {
1987            let (encoded, mask, nib) = match stack.pop() {
1988                Some(v) => v,
1989                None => return false,
1990            };
1991            if nib == TERMINAL_NIB {
1992                if self.bump_push_next(stack, encoded, mask, 0, lo, hi) {
1993                    return true;
1994                }
1995                continue;
1996            }
1997            let search_start = if nib == usize::MAX { 0 } else { nib + 1 };
1998            if self.bump_push_next(stack, encoded, mask, search_start, lo, hi) {
1999                return true;
2000            }
2001        }
2002    }
2003
2004    /// Wire the new key (at slot `p`) into the arena according to `case`, then
2005    /// propagate the leftmost-`leaf` invariant up the spine. Re-reads any
2006    /// bump-sensitive leaf key index from the arena (post-bump) instead of using
2007    /// a value captured before the bump — notably `SplitLeaf`'s existing key,
2008    /// which may have been the successor `p` and shifted to `p+1`.
2009    fn execute_case(&mut self, case: Case, p: usize, path: &[(usize, usize)]) {
2010        let p_idx = PTR::from_usize(p);
2011        match case {
2012            Case::Terminal { phys } => {
2013                self.inode_mut(phys).set_terminal(true);
2014                self.inode_mut(phys).leaf = OptNz::from_index(p_idx);
2015                self.up_walk_leftmost(phys, p_idx, path);
2016            }
2017            Case::NewLeafChild { phys, nib } => {
2018                self.inode_mut(phys).set_leaf_child(nib, p_idx);
2019                self.update_leftmost_on_leaf_insert(phys, nib, p_idx, path);
2020            }
2021            Case::SplitNode {
2022                phys,
2023                diverge,
2024                new_is_terminal,
2025                new_nib,
2026                ref_nib,
2027                new_is_leftmost,
2028            } => {
2029                let mut new_parent = Node::new();
2030                new_parent.prefix_len = LEN::from_usize(diverge);
2031                if new_is_terminal {
2032                    new_parent.set_terminal(true);
2033                    new_parent.leaf = OptNz::from_index(p_idx);
2034                } else {
2035                    new_parent.set_leaf_child(new_nib, p_idx);
2036                    if new_is_leftmost {
2037                        new_parent.leaf = OptNz::from_index(p_idx);
2038                    }
2039                }
2040                let old_node = std::mem::replace(&mut self.arena[phys], ArenaNode::Inode(new_parent));
2041                let old_addr = PTR::from_usize(self.arena.len()); // new node index (>= 1)
2042                self.arena.push(old_node);
2043                self.inode_mut(phys).set_internal_child(ref_nib, old_addr);
2044                if new_is_leftmost {
2045                    // New key is the new parent's leftmost — propagate up the spine.
2046                    // (Overrides the bump's p→p+1 on this spine back to p.)
2047                    self.up_walk_leftmost(phys, p_idx, path);
2048                } else {
2049                    // Old subtree is leftmost; its leftmost key index lives on in
2050                    // the pushed old node and was bumped there if in range.
2051                    let child_leaf = self.inode(old_addr.as_usize()).leaf;
2052                    self.inode_mut(phys).leaf = child_leaf;
2053                }
2054            }
2055            Case::SplitLeaf {
2056                phys,
2057                nib,
2058                d,
2059                new_is_terminal,
2060                existing_is_terminal,
2061                new_nib,
2062                exist_nib,
2063                new_is_leftmost,
2064            } => {
2065                // Re-read existing key index post-bump (may have been bumped
2066                // from p to p+1 if existing was the successor).
2067                let existing_key_index = self.inode(phys).children[nib].get();
2068                let mut split_node = Node::new();
2069                split_node.prefix_len = LEN::from_usize(d);
2070                if new_is_terminal {
2071                    split_node.set_terminal(true);
2072                    split_node.leaf = OptNz::from_index(p_idx);
2073                    split_node.set_leaf_child(exist_nib, existing_key_index);
2074                } else if existing_is_terminal {
2075                    split_node.set_terminal(true);
2076                    split_node.leaf = OptNz::from_index(existing_key_index);
2077                    split_node.set_leaf_child(new_nib, p_idx);
2078                } else {
2079                    split_node.set_leaf_child(new_nib, p_idx);
2080                    split_node.set_leaf_child(exist_nib, existing_key_index);
2081                    split_node.leaf = OptNz::from_index(if new_is_leftmost {
2082                        p_idx
2083                    } else {
2084                        existing_key_index
2085                    });
2086                }
2087                let split_addr = PTR::from_usize(self.arena.len());
2088                self.arena.push(ArenaNode::Inode(split_node));
2089                self.inode_mut(phys).set_internal_child(nib, split_addr);
2090                if new_is_leftmost {
2091                    // path.last() == (phys, nib): if split_node is phys's leftmost
2092                    // child, propagate the new leftmost up the spine.
2093                    self.up_walk_leftmost(split_addr.as_usize(), p_idx, path);
2094                }
2095            }
2096        }
2097    }
2098
2099    /// If the new leaf child at `nib` is the lowest occupied nib of `phys_idx`,
2100    /// it is the node's new leftmost descendant — set `phys_idx.leaf` and
2101    /// propagate the new leftmost up the leftmost spine via `path`.
2102    #[inline]
2103    fn update_leftmost_on_leaf_insert(
2104        &mut self,
2105        phys_idx: usize,
2106        nib: usize,
2107        new_index: PTR,
2108        path: &[(usize, usize)],
2109    ) {
2110        // A terminal node's own key is a prefix of all its descendants, so it
2111        // is always the leftmost — a new leaf child can never precede it.
2112        if self.inode(phys_idx).is_terminal() {
2113            return;
2114        }
2115        let mask = self.inode(phys_idx).children_mask();
2116        let lowest = mask.trailing_zeros() as usize;
2117        if nib == lowest {
2118            self.inode_mut(phys_idx).leaf = OptNz::from_index(new_index);
2119            self.up_walk_leftmost(phys_idx, new_index, path);
2120        }
2121    }
2122
2123    /// Propagate `new_leftmost` up the leftmost spine: for each ancestor in
2124    /// `path` (deepest first) via which we descended through that ancestor's
2125    /// lowest occupied nib, set its `leaf` to `new_leftmost`. Stop at the first
2126    /// ancestor where the descent was not through its leftmost child, OR at a
2127    /// terminal ancestor — a terminal node's own key is a prefix of all its
2128    /// descendants, so it is always that subtree's leftmost and its `leaf` must
2129    /// stay pinned to the terminal key's slot (ancestors above see that same
2130    /// fixed leftmost, so propagation stops entirely there).
2131    #[inline]
2132    fn up_walk_leftmost(&mut self, attach_phys: usize, new_leftmost: PTR, path: &[(usize, usize)]) {
2133        let _ = attach_phys; // attach's parent is path.last(); attach itself already set.
2134        let mut idx = path.len();
2135        while idx > 0 {
2136            idx -= 1;
2137            let (parent_phys, nib) = path[idx];
2138            if self.inode(parent_phys).is_terminal() {
2139                break;
2140            }
2141            let parent_mask = self.inode(parent_phys).children_mask();
2142            let lowest = parent_mask.trailing_zeros() as usize;
2143            if nib == lowest {
2144                self.inode_mut(parent_phys).leaf = OptNz::from_index(new_leftmost);
2145            } else {
2146                break;
2147            }
2148        }
2149    }
2150
2151    // -----------------------------------------------------------------------
2152    // Insert helpers
2153    // -----------------------------------------------------------------------
2154
2155    /// Empty trie: `index` is `[None]` (dummy at 0). Place the key at slot 1
2156    /// and build a root that is terminal (0-length key) or a leaf child.
2157    #[inline]
2158    fn insert_into_empty_trie(
2159        &mut self,
2160        off: usize,
2161        key_len: LEN,
2162        value: T,
2163        key: &[u8],
2164        max_nib: usize,
2165    ) -> usize {
2166        let p = 1usize;
2167        self.index
2168            .push(Some((NonZero::new(off).unwrap(), key_len, value)));
2169        let p_idx = PTR::from_usize(p);
2170        if max_nib == 0 {
2171            let mut root = Node::new();
2172            root.set_terminal(true);
2173            root.leaf = OptNz::from_index(p_idx);
2174            self.arena.push(ArenaNode::Inode(root));
2175        } else {
2176            let first_nib = key_nibble_at(key, 0) as usize;
2177            let mut root = Node::new();
2178            root.set_leaf_child(first_nib, p_idx);
2179            root.leaf = OptNz::from_index(p_idx);
2180            self.arena.push(ArenaNode::Inode(root));
2181        }
2182        p
2183    }
2184}
2185
2186// ---------------------------------------------------------------------------
2187// PTR width conversions (promote/demote)
2188// ---------------------------------------------------------------------------
2189
2190impl<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleTrie<K, T, PTR, LEN> {
2191    /// Promote the arena index type to a wider PTR.
2192    /// All child indices and leaf key indices are widened via `NewPTR::from_usize`.
2193    pub fn promote<NewPTR: TrieIndex>(self) -> NibbleTrie<K, T, NewPTR, LEN> {
2194        let arena = self.arena.into_iter().map(|node| node.promote()).collect();
2195        NibbleTrie {
2196            arena,
2197            buf: self.buf,
2198            index: self.index,
2199            n_keys: self.n_keys,
2200            _key: PhantomData,
2201        }
2202    }
2203
2204    /// Demote the arena index type to a narrower PTR.
2205    /// Returns `Err(self)` if any index doesn't fit in the narrower type.
2206    pub fn demote<NewPTR: TrieIndex>(self) -> Result<NibbleTrie<K, T, NewPTR, LEN>, Self> {
2207        if self.arena.len() > NewPTR::max_value() || self.index.len() > NewPTR::max_value() {
2208            return Err(self);
2209        }
2210        let arena = self.arena.into_iter().map(|node| {
2211            node.demote().expect("demote capacity check should have caught this")
2212        }).collect();
2213        Ok(NibbleTrie {
2214            arena,
2215            buf: self.buf,
2216            index: self.index,
2217            n_keys: self.n_keys,
2218            _key: PhantomData,
2219        })
2220    }
2221}
2222
2223
2224// ---------------------------------------------------------------------------
2225// Iterator
2226// ---------------------------------------------------------------------------
2227
2228/// Sentinel nib value meaning "positioned at the terminal value of this node."
2229const TERMINAL_NIB: usize = 16;
2230
2231/// A stack frame for [`NibbleIter`]. The root is always an [`Frame::Inode`]
2232/// (the root Inode); [`Frame::Fnode`] frames appear only below the root,
2233/// mirroring the "Fnodes only appear below the root" arena invariant.
2234#[derive(Clone, Copy)]
2235pub(crate) enum Frame<PTR: TrieIndex> {
2236    /// An Inode frame: `encoded` = arena index, `mask` = its child occupancy
2237    /// mask, `nib` = the current child nibble (0..16), `TERMINAL_NIB` for the
2238    /// node's own terminal, or `usize::MAX` for "parked before the first child"
2239    /// (the initial root frame).
2240    Inode { encoded: PTR, mask: u16, nib: usize },
2241    /// An Fnode frame: positioned on terminal position `pos` — `0` = `base`
2242    /// (only when the Fnode's `terminal` flag is set), `i+1` = array slot `i`
2243    /// (a non-NULL-offset slot). Pre-order (base then array slots) is sorted key
2244    /// order, so `pos` enumerates the Fnode's terminals ascending.
2245    Fnode { arena_idx: PTR, pos: usize },
2246}
2247
2248/// Internal tree-walking cursor (stack-based arena DFS). Used only for
2249/// `bump_walk`'s seek-positioning and to land the public `Cursor`'s `seek` on
2250/// a key in O(keylen). Public iteration uses the linear-scan [`Cursor`].
2251pub(crate) struct NibbleIter<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2252    trie: &'a NibbleTrie<K, T, PTR, LEN>,
2253    /// DFS stack of [`Frame`]s — `Inode` for the direct-addressed 16-slot nodes,
2254    /// `Fnode` for the flat leaf-pack nodes (a DAG leaf: walk terminals in
2255    /// slot order, skip `None` branch markers).
2256    pub(crate) stack: Vec<Frame<PTR>>,
2257}
2258
2259impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> NibbleIter<'a, K, T, PTR, LEN> {
2260    fn new(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2261        if trie.arena.is_empty() {
2262            return NibbleIter { trie, stack: Vec::new() };
2263        }
2264        let mask = trie.inode(0).children_mask();
2265        let nib = if trie.inode(0).is_terminal() { TERMINAL_NIB } else { usize::MAX };
2266        NibbleIter { trie, stack: vec![Frame::Inode { encoded: PTR::zero(), mask, nib }] }
2267    }
2268
2269    fn descend_first(&mut self, mut phys_idx: usize) {
2270        loop {
2271            // Fnode? Position at its first terminal and stop (an Fnode is a
2272            // leaf). Compute the position without holding an arena borrow
2273            // across the `stack.push`.
2274            let fnode_pos = match &self.trie.arena[phys_idx] {
2275                ArenaNode::Fnode(f) => Some(
2276                    f.first_terminal_pos()
2277                        .expect("descend_first: Fnode with no terminals"),
2278                ),
2279                ArenaNode::Inode(_) => None,
2280            };
2281            if let Some(pos) = fnode_pos {
2282                self.stack.push(Frame::Fnode { arena_idx: PTR::from_usize(phys_idx), pos });
2283                return;
2284            }
2285            // Inode: copy out (Node: Copy) so no borrow is held across `push`.
2286            let node = *self.trie.inode(phys_idx);
2287            if node.is_terminal() {
2288                let mask = node.children_mask();
2289                self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib: TERMINAL_NIB });
2290                return;
2291            }
2292            let mask = node.children_mask();
2293            debug_assert!(mask != 0, "descend_first: non-terminal node with no children");
2294            let nib = mask.trailing_zeros() as usize;
2295            self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib });
2296            if node.is_leaf(nib) {
2297                return;
2298            }
2299            phys_idx = node.children[nib].get().as_usize();
2300        }
2301    }
2302
2303    #[inline]
2304    fn push_next_child(&mut self, encoded: PTR, mask: u16, start_nib: usize) -> bool {
2305        let shifted = if start_nib >= 16 { 0u16 } else { mask >> start_nib };
2306        if shifted == 0 {
2307            return false;
2308        }
2309        let nib = start_nib + shifted.trailing_zeros() as usize;
2310        debug_assert!(nib < 16);
2311        debug_assert!(mask & (1 << nib) != 0);
2312        let phys_idx = encoded.as_usize();
2313        // `encoded` is the parent Inode (Fnodes never have children, so a frame
2314        // passed here is always an Inode). Copy it out to release the borrow
2315        // before `push`.
2316        let node = *self.trie.inode(phys_idx);
2317        self.stack.push(Frame::Inode { encoded, mask, nib });
2318        if !node.is_leaf(nib) {
2319            let addr = node.children[nib].get().as_usize();
2320            self.descend_first(addr);
2321        }
2322        true
2323    }
2324
2325    #[inline]
2326    fn backtrack_to_next(&mut self) -> Option<(&[u8], &T)> {
2327        loop {
2328            let frame = self.stack.pop()?;
2329            match frame {
2330                Frame::Inode { encoded, mask, nib } => {
2331                    if self.push_next_child(encoded, mask, nib + 1) {
2332                        return self.current();
2333                    }
2334                }
2335                Frame::Fnode { .. } => {
2336                    // Exhausted Fnode frame left on the stack — skip it (the pop
2337                    // above already consumed it) and continue backtracking.
2338                    continue;
2339                }
2340            }
2341        }
2342    }
2343
2344    pub fn current(&self) -> Option<(&[u8], &T)> {
2345        let frame = self.stack.last()?;
2346        match *frame {
2347            Frame::Inode { encoded, nib, .. } => {
2348                if nib == usize::MAX {
2349                    return None;
2350                }
2351                let phys_idx = encoded.as_usize();
2352                let node = self.trie.inode(phys_idx);
2353                if nib == TERMINAL_NIB {
2354                    let ki = node.leaf.get();
2355                    let (off, len, val) = self.trie.index[ki.as_usize()].as_ref().unwrap();
2356                    let off = off.get();
2357                    Some((&self.trie.buf[off..off + len.as_usize()], val))
2358                } else if let Some(key_index) = node.leaf_key_index(nib) {
2359                    Some((self.trie.key_slice(key_index), &self.trie.index[key_index.as_usize()].as_ref().unwrap().2))
2360                } else {
2361                    None
2362                }
2363            }
2364            Frame::Fnode { arena_idx, pos } => {
2365                let f = match &self.trie.arena[arena_idx.as_usize()] {
2366                    ArenaNode::Fnode(f) => f,
2367                    ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2368                };
2369                let ki = f
2370                    .pos_key_index(pos)
2371                    .expect("current: Fnode frame positioned on a non-terminal");
2372                Some((self.trie.key_slice(ki), &self.trie.index[ki.as_usize()].as_ref().unwrap().2))
2373            }
2374        }
2375    }
2376
2377    pub fn current_index(&self) -> Option<usize> {
2378        let frame = self.stack.last()?;
2379        match *frame {
2380            Frame::Inode { encoded, nib, .. } => {
2381                if nib == usize::MAX {
2382                    return None;
2383                }
2384                let phys_idx = encoded.as_usize();
2385                let node = self.trie.inode(phys_idx);
2386                if nib == TERMINAL_NIB {
2387                    Some(node.leaf.get().as_usize())
2388                } else {
2389                    node.leaf_key_index(nib).map(|ki| ki.as_usize())
2390                }
2391            }
2392            Frame::Fnode { arena_idx, pos } => {
2393                let f = match &self.trie.arena[arena_idx.as_usize()] {
2394                    ArenaNode::Fnode(f) => f,
2395                    ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2396                };
2397                Some(
2398                    f.pos_key_index(pos)
2399                        .expect("current_index: Fnode frame positioned on a non-terminal")
2400                        .as_usize(),
2401                )
2402            }
2403        }
2404    }
2405
2406    #[inline]
2407    fn advance_next(&mut self) -> bool {
2408        loop {
2409            let frame = match self.stack.pop() {
2410                Some(v) => v,
2411                None => return false,
2412            };
2413            match frame {
2414                Frame::Inode { encoded, mask, nib } => {
2415                    if nib == TERMINAL_NIB {
2416                        if self.push_next_child(encoded, mask, 0) {
2417                            return true;
2418                        }
2419                        continue;
2420                    }
2421                    let search_start = if nib == usize::MAX { 0 } else { nib + 1 };
2422                    if self.push_next_child(encoded, mask, search_start) {
2423                        return true;
2424                    }
2425                    // `push_next_child` returned false and this frame is already
2426                    // popped — loop to pop the next frame and backtrack from it.
2427                    continue;
2428                }
2429                Frame::Fnode { arena_idx, pos } => {
2430                    // Advance to the next terminal position in this Fnode (base
2431                    // then array terminal slots). Extract the result without
2432                    // holding an arena borrow across `push`.
2433                    let next_pos = match &self.trie.arena[arena_idx.as_usize()] {
2434                        ArenaNode::Fnode(f) => f.next_terminal_pos(pos),
2435                        ArenaNode::Inode(_) => unreachable!("Fnode frame points at an Inode"),
2436                    };
2437                    if let Some(np) = next_pos {
2438                        self.stack.push(Frame::Fnode { arena_idx, pos: np });
2439                        return true;
2440                    }
2441                    // Exhausted Fnode: this frame is already popped — loop to
2442                    // pop the parent Inode frame and backtrack from there.
2443                    continue;
2444                }
2445            }
2446        }
2447    }
2448
2449    #[inline]
2450    pub fn next(&mut self) -> Option<(&[u8], &T)> {
2451        if self.advance_next() { self.current() } else { None }
2452    }
2453
2454    /// Seek within an Fnode child for the first terminal key ≥ `key`. The parent
2455    /// Inode frame is already on the stack (pushed by [`seek`](Self::seek) before
2456    /// dispatching here). On a hit, push an [`Frame::Fnode`] and return `current`.
2457    /// On exhaust (all Fnode terminals < `key`), pop the parent and backtrack to
2458    /// its next child.
2459    fn fnode_seek(&mut self, arena_idx: usize, key: &[u8], _max_nib: usize) -> Option<(&[u8], &T)> {
2460        // Pre-order (base then array slots) == sorted key order: the first
2461        // terminal whose key is ≥ `key` is the lower bound. Scan inside a block
2462        // so the arena borrow ends before the `stack.push` below.
2463        let found_pos: Option<usize> = {
2464            let f = match &self.trie.arena[arena_idx] {
2465                ArenaNode::Fnode(f) => f,
2466                ArenaNode::Inode(_) => unreachable!("fnode_seek on an Inode"),
2467            };
2468            // `base` first (if it is a terminal).
2469            if f.terminal && self.trie.key_slice(f.base) >= key {
2470                Some(0)
2471            } else {
2472                // Array terminal slots in pre-order (ascending key order).
2473                let slots = f.slots.as_slice();
2474                let base = f.base.as_usize();
2475                let mut found = None;
2476                for (i, (_plen, offset)) in slots.iter().enumerate() {
2477                    if *offset == FNODE_OFFSET_NULL {
2478                        continue;
2479                    }
2480                    let ki = PTR::from_usize(base + *offset as usize);
2481                    if self.trie.key_slice(ki) >= key {
2482                        found = Some(i + 1);
2483                        break;
2484                    }
2485                }
2486                found
2487            }
2488        };
2489        if let Some(pos) = found_pos {
2490            self.stack.push(Frame::Fnode { arena_idx: PTR::from_usize(arena_idx), pos });
2491            return self.current();
2492        }
2493        // All Fnode terminals < key → backtrack to the parent Inode's next child.
2494        match self.stack.pop() {
2495            Some(Frame::Inode { encoded, mask, nib }) => {
2496                if self.push_next_child(encoded, mask, nib + 1) {
2497                    return self.current();
2498                }
2499                self.backtrack_to_next()
2500            }
2501            other => {
2502                // The root is never an Fnode and `seek` always pushes the parent
2503                // Inode frame before dispatching, so there must be one. Defensively
2504                // restore anything unexpected and report no match.
2505                if let Some(frm) = other {
2506                    self.stack.push(frm);
2507                }
2508                None
2509            }
2510        }
2511    }
2512
2513    pub fn seek(&mut self, key: &[u8]) -> Option<(&[u8], &T)> {
2514        if self.trie.arena.is_empty() {
2515            self.stack.clear();
2516            return None;
2517        }
2518
2519        self.stack.clear();
2520        let mut phys_idx: usize = 0;
2521        let max_nib = key.len() * 2;
2522
2523        loop {
2524            // Root and every ephemeral-descent target are Inodes — Fnode children
2525            // are dispatched below and `return` before re-looping. Copy the node
2526            // out (Node: Copy) so no borrow is held across `stack.push` /
2527            // recursive `self.` calls.
2528            let node = *self.trie.inode(phys_idx);
2529            let mask = node.children_mask();
2530
2531            if node.is_terminal() && node.prefix_len.as_usize() >= max_nib {
2532                let ki = node.leaf.get();
2533                let (off, len, _) = self.trie.index[ki.as_usize()].as_ref().unwrap();
2534                let off = off.get();
2535                let node_key = &self.trie.buf[off..off + len.as_usize()];
2536                if node_key >= key {
2537                    self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib: TERMINAL_NIB });
2538                    return self.current();
2539                }
2540            }
2541
2542            if node.prefix_len.as_usize() >= max_nib {
2543                if self.push_next_child(PTR::from_usize(phys_idx), mask, 0) {
2544                    return self.current();
2545                }
2546                return self.backtrack_to_next();
2547            }
2548
2549            let nib = key_nibble_at(key, node.prefix_len.as_usize()) as usize;
2550            if !node.is_occupied(nib) {
2551                // No child at this nibble — find next higher child, or backtrack
2552                if self.push_next_child(PTR::from_usize(phys_idx), mask, nib + 1) {
2553                    return self.current();
2554                }
2555                return self.backtrack_to_next();
2556            }
2557
2558            self.stack.push(Frame::Inode { encoded: PTR::from_usize(phys_idx), mask, nib });
2559            if node.is_leaf(nib) {
2560                let leaf_key = self.trie.key_slice(node.children[nib].get());
2561                if leaf_key >= key {
2562                    return self.current();
2563                }
2564                // Leaf key < seek key: advance past it
2565                return self.next();
2566            } else {
2567                let child_addr = node.children[nib].get().as_usize();
2568                if matches!(self.trie.arena[child_addr], ArenaNode::Fnode(_)) {
2569                    // Parent Inode frame already pushed above. Flat-seek the
2570                    // Fnode: either land on a terminal ≥ key, or backtrack out.
2571                    return self.fnode_seek(child_addr, key, max_nib);
2572                }
2573                phys_idx = child_addr;
2574            }
2575        }
2576    }
2577}
2578
2579// ---------------------------------------------------------------------------
2580// Cursor — public linear-scan iterator over the sparse `index`
2581// ---------------------------------------------------------------------------
2582
2583/// Public iteration cursor over a [`NibbleTrie`]: a linear scan of the sparse
2584/// `index`, skipping `None` gaps. This is correct because the index is kept
2585/// sorted by invariant — occupied slots appear in non-decreasing key order
2586/// (enforced by the Stage B shift-and-bump insert, and checked by the
2587/// invariant-oracle tests).
2588///
2589/// `iter()` parks *before* the first key (`current()` is `None`, `next()`
2590/// yields the first key — the idiomatic `Iterator` model); `iter_last()` parks
2591/// *on* the last key (`current()` returns it, `prev()` walks backward). `seek`
2592/// lands in O(keylen) via the internal tree walker, then `next`/`prev` resume
2593/// the linear scan. `first`/`last` jump to the ends. The current key/value is
2594/// cached at park time, so `current()` (and a `next().current()` follow-up) is
2595/// a pure field read with no re-scan.
2596///
2597/// The cached refs borrow the trie (lifetime `'a`), not the cursor, so the
2598/// `&'a T` returned by `current`/`next`/`prev`/`seek` outlives the cursor
2599/// borrow. The key is returned as [`ByteKey::Borrowed<'a>`] (via
2600/// [`ByteKey::as_borrowed`]) — a zero-allocation view into the trie's key
2601/// buffer (`&'a [u8]` for `Vec<u8>` keys, `&'a str` for
2602/// `String` keys). The slice is cached internally, so `current()`/`next()` pay
2603/// only the `as_borrowed` view (no allocation, no re-scan).
2604pub struct Cursor<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2605    trie: &'a NibbleTrie<K, T, PTR, LEN>,
2606    /// Slot index parked on, or a sentinel: `0` = before-first / backward
2607    /// exhausted (slot 0 is the dummy `None`), `index.len()` = forward
2608    /// exhausted. A parked `pos` is always a `Some` slot in `[1, len-1]`.
2609    pos: usize,
2610    /// Cached `current()` value: `Some` iff `pos` is a `Some` slot.
2611    cur: Option<(&'a [u8], &'a T)>,
2612}
2613
2614impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Cursor<'a, K, T, PTR, LEN> {
2615    /// Park at a known-occupied slot, building the cached current value
2616    /// directly from the already-fetched `slot` ref. `slot` borrows the trie
2617    /// with lifetime `'a` (so the cached refs carry `'a`, not the cursor
2618    /// borrow) — safe to then write `self.cur`. The caller has already proven
2619    /// the slot is `Some`, so this is branch-free apart from the slice bounds.
2620    #[inline]
2621    fn park_slot(&mut self, pos: usize, slot: &'a Slot<LEN, T>) {
2622        self.pos = pos;
2623        let off = slot.0.get();
2624        let klen = slot.1.as_usize();
2625        self.cur = Some((&self.trie.buf[off..off + klen], &slot.2));
2626    }
2627
2628    /// Park at a sentinel (`0` = before-first / backward exhausted, or `len` =
2629    /// forward exhausted): no live key, so the cached current is `None`.
2630    #[inline]
2631    fn park_sentinel(&mut self, pos: usize) {
2632        self.pos = pos;
2633        self.cur = None;
2634    }
2635
2636    /// Forward cursor parked *before* the first key.
2637    pub fn new(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2638        Cursor { trie, pos: 0, cur: None }
2639    }
2640
2641    /// Reverse cursor parked *on* the last key (or before-first if empty).
2642    pub fn new_last(trie: &'a NibbleTrie<K, T, PTR, LEN>) -> Self {
2643        let mut c = Cursor { trie, pos: 0, cur: None };
2644        c.last();
2645        c
2646    }
2647
2648    /// Jump to the first key (smallest slot). Returns its key/value, or `None`
2649    /// if the trie is empty. Scans forward from slot 1.
2650    pub fn first(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2651        let len = self.trie.index.len();
2652        let mut i = 1;
2653        while i < len {
2654            if let Some(slot) = self.trie.index[i].as_ref() {
2655                self.park_slot(i, slot);
2656                return self.cur.map(|(k, v)| (K::as_borrowed(k), v));
2657            }
2658            i += 1;
2659        }
2660        self.park_sentinel(0);
2661        None
2662    }
2663
2664    /// Jump to the last key (largest slot). Returns its key/value, or `None` if
2665    /// the trie is empty. Scans backward from the end of `index`.
2666    pub fn last(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2667        let mut i = self.trie.index.len();
2668        while i > 1 {
2669            i -= 1;
2670            if let Some(slot) = self.trie.index[i].as_ref() {
2671                self.park_slot(i, slot);
2672                return self.cur.map(|(k, v)| (K::as_borrowed(k), v));
2673            }
2674        }
2675        self.park_sentinel(0);
2676        None
2677    }
2678
2679    /// The key/value the cursor is parked on, or `None` if not parked (before
2680    /// first, or exhausted). A pure field read — the slice/value pair is cached
2681    /// by `park`; only the zero-alloc `as_borrowed` view runs per call.
2682    #[inline]
2683    pub fn current(&self) -> Option<(K::Borrowed<'a>, &'a T)> {
2684        self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2685    }
2686
2687    /// The slot index the cursor is parked on, or `None` if not parked.
2688    #[inline]
2689    pub fn current_index(&self) -> Option<usize> {
2690        if self.cur.is_some() { Some(self.pos) } else { None }
2691    }
2692
2693    /// Advance to the next occupied slot and return its key/value. Returns
2694    /// `None` (parking at the forward-exhausted sentinel) when no further key
2695    /// exists.
2696    #[inline]
2697    pub fn next(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2698        if self.advance_next() {
2699            self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2700        } else {
2701            None
2702        }
2703    }
2704
2705    /// Step to the previous occupied slot and return its key/value. Returns
2706    /// `None` (parking at the before-first sentinel) when no prior key exists.
2707    #[inline]
2708    pub fn prev(&mut self) -> Option<(K::Borrowed<'a>, &'a T)> {
2709        if self.advance_prev() {
2710            self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2711        } else {
2712            None
2713        }
2714    }
2715
2716    #[inline]
2717    pub fn next_index(&mut self) -> Option<usize> {
2718        if self.advance_next() { Some(self.pos) } else { None }
2719    }
2720
2721    #[inline]
2722    pub fn prev_index(&mut self) -> Option<usize> {
2723        if self.advance_prev() { Some(self.pos) } else { None }
2724    }
2725
2726    /// Land on the first key ≥ `key` — O(keylen) via the internal tree walker —
2727    /// then return its key/value. Returns `None` if no key is ≥ `key`.
2728    pub fn seek(&mut self, key: &[u8]) -> Option<(K::Borrowed<'a>, &'a T)> {
2729        let pos = {
2730            let mut w = self.trie.walk_iter();
2731            w.seek(key);
2732            w.current_index()
2733        };
2734        match pos {
2735            Some(p) => {
2736                // `p` is a tree-walker-confirmed occupied slot.
2737                if let Some(slot) = self.trie.index[p].as_ref() {
2738                    self.park_slot(p, slot);
2739                    self.cur.map(|(k, v)| (K::as_borrowed(k), v))
2740                } else {
2741                    self.park_sentinel(self.trie.index.len());
2742                    None
2743                }
2744            }
2745            None => { self.park_sentinel(self.trie.index.len()); None }
2746        }
2747    }
2748
2749    // --- core linear scans ---
2750
2751    /// Scan forward from `pos+1` to the next `Some` slot; park there on hit,
2752    /// or at the `len` sentinel on miss. Each slot is fetched once and, on a
2753    /// hit, handed straight to `park_slot` — no second `index` load and no
2754    /// re-match of the `Option` (which the old `park` did).
2755    #[inline]
2756    fn advance_next(&mut self) -> bool {
2757        let len = self.trie.index.len();
2758        let mut i = self.pos + 1;
2759        while i < len {
2760            if let Some(slot) = self.trie.index[i].as_ref() {
2761                self.park_slot(i, slot);
2762                return true;
2763            }
2764            i += 1;
2765        }
2766        self.park_sentinel(len);
2767        false
2768    }
2769
2770    /// Scan backward from `pos-1` to the previous `Some` slot; park there on
2771    /// hit, or at the `0` (before-first) sentinel on miss. Same single-fetch
2772    /// strategy as `advance_next`.
2773    #[inline]
2774    fn advance_prev(&mut self) -> bool {
2775        let mut i = self.pos;
2776        while i > 1 {
2777            i -= 1;
2778            if let Some(slot) = self.trie.index[i].as_ref() {
2779                self.park_slot(i, slot);
2780                return true;
2781            }
2782        }
2783        self.park_sentinel(0);
2784        false
2785    }
2786}
2787
2788// ---------------------------------------------------------------------------
2789// CursorMut — public linear-scan iterator lending out &mut T
2790// ---------------------------------------------------------------------------
2791
2792/// Mutable counterpart to [`Cursor`]: a linear scan of the sparse `index`
2793/// that lends out `&mut T` borrows over the stored values.
2794///
2795/// Unlike [`Cursor`], the value reference is tied to `&mut self` (a *lending*
2796/// cursor), not to the trie lifetime `'a`. This is a soundness requirement, not
2797/// a stylistic choice: a cursor is re-positionable — `current()`, `seek()`,
2798/// `first()`, `last()` can all revisit a slot already visited. An `'a`-tied
2799/// `&mut T` (as the immutable cursor hands out `&'a T`) would let two such
2800/// calls return `&'a mut T` to the *same* element simultaneously — aliasing
2801/// undefined behavior. Tying the borrow to `&mut self` makes the borrow checker
2802/// enforce "one live `&mut T` at a time," which is the only sound rule for a
2803/// re-positionable mutable cursor. The practical consequence: you cannot
2804/// collect the `&mut T` into a `Vec` or hold two at once; each must be released
2805/// before the next `next()`/`prev()`/`current()`/`seek()` call. In-place
2806/// mutation loops (`while let Some((k, v)) = c.next() { *v += 1; }`) work as
2807/// expected.
2808///
2809/// The key is returned as [`ByteKey::Borrowed<'_>`] (via [`ByteKey::as_borrowed`])
2810/// — a zero-alloc view into the trie's key buffer, tied to the same `&mut self`
2811/// borrow as the `&mut T` (so it, too, must be released before the next call).
2812/// Only the stored *value* is mutated; the cursor never alters key bytes, node
2813/// structure, or slot occupancy, so trie invariants are preserved.
2814pub struct CursorMut<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2815    trie: &'a mut NibbleTrie<K, T, PTR, LEN>,
2816    /// Slot index parked on, or a sentinel: `0` = before-first / backward
2817    /// exhausted (slot 0 is the dummy `None`), `index.len()` = forward
2818    /// exhausted. A parked `pos` is always a `Some` slot in `[1, len-1]`.
2819    pos: usize,
2820}
2821
2822impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> CursorMut<'a, K, T, PTR, LEN> {
2823    /// Forward mutable cursor parked *before* the first key.
2824    pub fn new(trie: &'a mut NibbleTrie<K, T, PTR, LEN>) -> Self {
2825        CursorMut { trie, pos: 0 }
2826    }
2827
2828    /// Reverse mutable cursor parked *on* the last key (or before-first if
2829    /// empty).
2830    pub fn new_last(trie: &'a mut NibbleTrie<K, T, PTR, LEN>) -> Self {
2831        let mut c = CursorMut { trie, pos: 0 };
2832        c.last();
2833        c
2834    }
2835
2836    /// Build the `(K::Borrowed<'_>, &mut T)` pair for the slot at `self.pos`.
2837    /// The `pos` must be a parked, occupied slot. Three sequential borrows that
2838    /// the borrow checker sees as disjoint fields of `*self.trie`: (1) immutable
2839    /// peek of the slot for `off`/`len` (copied out as `usize`, borrow ends),
2840    /// (2) immutable read of `buf` for the borrowed key view (held for `'b` in
2841    /// the return), (3) mutable borrow of the slot for `&mut T` (held for `'b`).
2842    /// The `buf` (shared) and `index` (mutable) borrows coexist on disjoint
2843    /// fields. Both are tied to `&mut self` — the lending contract in the type
2844    /// docs.
2845    #[inline]
2846    fn materialize<'b>(&'b mut self) -> Option<(K::Borrowed<'b>, &'b mut T)> {
2847        let pos = self.pos;
2848        let (off, len) = {
2849            let slot = self.trie.index[pos].as_ref()?;
2850            (slot.0.get(), slot.1.as_usize())
2851        };
2852        let k = K::as_borrowed(&self.trie.buf[off..off + len]);
2853        let slot = self.trie.index[pos].as_mut()?;
2854        Some((k, &mut slot.2))
2855    }
2856
2857    /// Jump to the first key (smallest slot). Returns its key/value, or `None`
2858    /// if the trie is empty. Scans forward from slot 1.
2859    pub fn first(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2860        self.pos = 0;
2861        if self.advance_next() { self.materialize() } else { None }
2862    }
2863
2864    /// Jump to the last key (largest slot). Returns its key/value, or `None` if
2865    /// the trie is empty. Scans backward from the end of `index`.
2866    pub fn last(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2867        self.pos = self.trie.index.len();
2868        if self.advance_prev() { self.materialize() } else { None }
2869    }
2870
2871    /// The key/value the cursor is parked on, or `None` if not parked (before
2872    /// first, or exhausted). Reconstructs `K` and reborrows `&mut T` per call.
2873    #[inline]
2874    pub fn current(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2875        let len = self.trie.index.len();
2876        if self.pos == 0 || self.pos >= len {
2877            return None;
2878        }
2879        self.materialize()
2880    }
2881
2882    /// The slot index the cursor is parked on, or `None` if not parked.
2883    #[inline]
2884    pub fn current_index(&self) -> Option<usize> {
2885        let len = self.trie.index.len();
2886        if self.pos != 0 && self.pos < len { Some(self.pos) } else { None }
2887    }
2888
2889    /// Advance to the next occupied slot and return its key/value. Returns
2890    /// `None` (parking at the forward-exhausted sentinel) when no further key
2891    /// exists.
2892    #[inline]
2893    pub fn next(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2894        if self.advance_next() { self.materialize() } else { None }
2895    }
2896
2897    /// Step to the previous occupied slot and return its key/value. Returns
2898    /// `None` (parking at the before-first sentinel) when no prior key exists.
2899    #[inline]
2900    pub fn prev(&mut self) -> Option<(K::Borrowed<'_>, &mut T)> {
2901        if self.advance_prev() { self.materialize() } else { None }
2902    }
2903
2904    #[inline]
2905    pub fn next_index(&mut self) -> Option<usize> {
2906        if self.advance_next() { Some(self.pos) } else { None }
2907    }
2908
2909    #[inline]
2910    pub fn prev_index(&mut self) -> Option<usize> {
2911        if self.advance_prev() { Some(self.pos) } else { None }
2912    }
2913
2914    /// Land on the first key ≥ `key` — O(keylen) via the internal tree walker —
2915    /// then return its key/value. Returns `None` if no key is ≥ `key`.
2916    pub fn seek(&mut self, key: &[u8]) -> Option<(K::Borrowed<'_>, &mut T)> {
2917        let pos = {
2918            let trie = &*self.trie;
2919            let mut w = trie.walk_iter();
2920            w.seek(key);
2921            w.current_index()
2922        };
2923        let len = self.trie.index.len();
2924        match pos {
2925            Some(p) if self.trie.index[p].is_some() => {
2926                self.pos = p;
2927                self.materialize()
2928            }
2929            _ => { self.pos = len; None }
2930        }
2931    }
2932
2933    // --- core linear scans (position only; no borrow handed out) ---
2934
2935    /// Scan forward from `pos+1` to the next `Some` slot; park there on hit,
2936    /// or at the `len` sentinel on miss. Only updates `pos` — no value borrow
2937    /// is taken, so the caller can then `materialize` a fresh `&mut T`.
2938    #[inline]
2939    fn advance_next(&mut self) -> bool {
2940        let len = self.trie.index.len();
2941        let mut i = self.pos + 1;
2942        while i < len {
2943            if self.trie.index[i].is_some() {
2944                self.pos = i;
2945                return true;
2946            }
2947            i += 1;
2948        }
2949        self.pos = len;
2950        false
2951    }
2952
2953    /// Scan backward from `pos-1` to the previous `Some` slot; park there on
2954    /// hit, or at the `0` sentinel on miss. Only updates `pos`.
2955    #[inline]
2956    fn advance_prev(&mut self) -> bool {
2957        let mut i = self.pos;
2958        while i > 1 {
2959            i -= 1;
2960            if self.trie.index[i].is_some() {
2961                self.pos = i;
2962                return true;
2963            }
2964        }
2965        self.pos = 0;
2966        false
2967    }
2968}
2969
2970// ---------------------------------------------------------------------------
2971// Range — zero-alloc ascending iterator over a key interval
2972// ---------------------------------------------------------------------------
2973
2974/// Ascending iterator over a half-open key interval of a [`NibbleTrie`],
2975/// yielding `(K::Borrowed<'a>, &'a T)` with no allocation.
2976///
2977/// Constructed via [`NibbleTrie::range`]. Both bounds are resolved to slot
2978/// indices with O(keylen) seeks at construction time; iteration is then a
2979/// linear scan of the sparse `index` bounded by `pos < end_pos` (a `usize`
2980/// compare), so no per-element key comparison runs. `None` gaps between
2981/// `start_pos` and `end_pos` are skipped. The item borrows the trie (`'a`), not
2982/// the iterator, so [`Iterator`] is implemented directly (not lending).
2983///
2984/// Bound semantics match `BTreeMap::range`:
2985/// - `Included(k)` lower → first key ≥ `k`; upper → include keys ≤ `k`.
2986/// - `Excluded(k)` lower → first key > `k`; upper → include keys < `k`.
2987/// - `Unbounded` lower → first key; upper → last key.
2988pub struct Range<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> {
2989    trie: &'a NibbleTrie<K, T, PTR, LEN>,
2990    /// Next slot index to scan from. `0` = before-first; `end_pos` = exhausted.
2991    pos: usize,
2992    /// Exclusive upper slot bound: yield occupied slots with index `< end_pos`.
2993    end_pos: usize,
2994}
2995
2996impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Range<'a, K, T, PTR, LEN> {
2997    /// Build a `Range` from `(start, end)` bounds. Each concrete bound costs one
2998    /// O(keylen) seek; `Unbounded` bounds are free.
2999    pub(crate) fn new(
3000        trie: &'a NibbleTrie<K, T, PTR, LEN>,
3001        start: Bound<&[u8]>,
3002        end: Bound<&[u8]>,
3003    ) -> Self {
3004        let len = trie.index.len();
3005        // Lower bound → first slot to yield.
3006        let pos = match start {
3007            Bound::Included(k) => ceiling_index(trie, k).unwrap_or(len),
3008            Bound::Excluded(k) => ceiling_strict_index(trie, k).unwrap_or(len),
3009            Bound::Unbounded => 0, // slot 0 is the dummy None; scan skips it.
3010        };
3011        // Upper bound → exclusive index of the first key to exclude.
3012        let end_pos = match end {
3013            Bound::Included(k) => ceiling_strict_index(trie, k).unwrap_or(len),
3014            Bound::Excluded(k) => ceiling_index(trie, k).unwrap_or(len),
3015            Bound::Unbounded => len,
3016        };
3017        Range { trie, pos, end_pos }
3018    }
3019}
3020
3021impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> Iterator for Range<'a, K, T, PTR, LEN> {
3022    type Item = (K::Borrowed<'a>, &'a T);
3023
3024    #[inline]
3025    fn next(&mut self) -> Option<Self::Item> {
3026        let end = self.end_pos;
3027        let mut i = self.pos;
3028        while i < end {
3029            if let Some(slot) = self.trie.index[i].as_ref() {
3030                let off = slot.0.get();
3031                let klen = slot.1.as_usize();
3032                let k = K::as_borrowed(&self.trie.buf[off..off + klen]);
3033                self.pos = i + 1;
3034                return Some((k, &slot.2));
3035            }
3036            i += 1;
3037        }
3038        self.pos = end;
3039        None
3040    }
3041
3042    #[inline]
3043    fn size_hint(&self) -> (usize, Option<usize>) {
3044        // Upper bound: at most `end_pos - pos` slots (gaps reduce the true
3045        // count). A precise count would require scanning, which defeats the
3046        // point, so report only the loose upper bound.
3047        let remaining = self.end_pos.saturating_sub(self.pos);
3048        (0, Some(remaining))
3049    }
3050}
3051
3052// `Range::next` only reads `index` and `buf` through the shared `&'a NibbleTrie`
3053// borrow, so it is safe to hand out items that outlive the `&mut self` of
3054// `next` — hence a true `Iterator`, not a lending one.
3055impl<'a, K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex> DoubleEndedIterator
3056    for Range<'a, K, T, PTR, LEN>
3057{
3058    /// Walk backward from `end_pos - 1` to `pos`, yielding the largest occupied
3059    /// slot still in range. `next_back` and `next` stay consistent because both
3060    /// close in on the same `[pos, end_pos)` span.
3061    #[inline]
3062    fn next_back(&mut self) -> Option<Self::Item> {
3063        let start = self.pos;
3064        let mut i = self.end_pos;
3065        while i > start {
3066            i -= 1;
3067            if let Some(slot) = self.trie.index[i].as_ref() {
3068                let off = slot.0.get();
3069                let klen = slot.1.as_usize();
3070                let k = K::as_borrowed(&self.trie.buf[off..off + klen]);
3071                self.end_pos = i;
3072                return Some((k, &slot.2));
3073            }
3074        }
3075        self.end_pos = start;
3076        None
3077    }
3078}
3079
3080/// Slot index of the first occupied slot with key ≥ `key` (the ceiling), via
3081/// the O(keylen) tree walker. `None` if no key is ≥ `key`.
3082fn ceiling_index<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex>(
3083    trie: &NibbleTrie<K, T, PTR, LEN>,
3084    key: &[u8],
3085) -> Option<usize> {
3086    let mut w = trie.walk_iter();
3087    w.seek(key);
3088    w.current_index()
3089}
3090
3091/// Slot index of the first occupied slot with key strictly > `key`. Seeks to the
3092/// ceiling of `key`; if that slot's key equals `key`, advances to the next
3093/// occupied slot. `None` if no such key exists.
3094fn ceiling_strict_index<K: ByteKey, T, PTR: TrieIndex, LEN: TrieIndex>(
3095    trie: &NibbleTrie<K, T, PTR, LEN>,
3096    key: &[u8],
3097) -> Option<usize> {
3098    let p = ceiling_index(trie, key)?;
3099    let slot = trie.index[p].as_ref()?;
3100    let off = slot.0.get();
3101    let klen = slot.1.as_usize();
3102    if &trie.buf[off..off + klen] == key {
3103        // The ceiling is `key` itself; the strict ceiling is the next occupied
3104        // slot after it.
3105        let len = trie.index.len();
3106        let mut i = p + 1;
3107        while i < len {
3108            if trie.index[i].is_some() {
3109                return Some(i);
3110            }
3111            i += 1;
3112        }
3113        None
3114    } else {
3115        Some(p)
3116    }
3117}
3118
3119#[cfg(test)]
3120#[path = "tests/nibble_trie.rs"]
3121mod tests;