//! Cursors for sequential and random access over a [`Rope`].
//!
//! # Design
//!
//! A cursor borrows a `&'a Rope` and keeps a small block cache so that
//! consecutive accesses within the same block cost O(1). The first access to a
//! new block costs O(log n) (binary search in [`Rope::lookup`]); subsequent
//! accesses within that block are free.
//!
//! All mutable state (current position, cache) is stored in [`Cell`] fields,
//! so every cursor method takes `&self` rather than `&mut self`. This means:
//!
//! - Two cursors can coexist on the same rope without lifetime conflicts.
//! - The `iter()` method returns a lightweight wrapper that holds `&Cursor`,
//!   allowing `cursor.tell()` or `cursor.seek()` to be called **inside a `for`
//!   loop** over the same cursor.
//!
//! # Cursors
//!
//! | Type | Direction | First `read_next` | `seek(Relative, +n)` |
//! |------|-----------|-------------------|----------------------|
//! | [`ForwardCursor`] | start → end | index 0 | advances (+n) |
//! | [`BackwardCursor`] | end → start | index `len-1` | retreats (+n) |
//!
//! # Example
//!
//! ```
//! use obikrope::{Rope, RopeCursor};
//!
//! let mut rope = Rope::new(None);
//! rope.push(b"ACGT".to_vec());
//!
//! let cursor = rope.fw_cursor();
//! for byte in cursor.iter() {
//!     // cursor.tell() is valid here — iter() holds &cursor, not &mut cursor
//!     let _ = cursor.tell();
//! }
//! ```

use std::cell::Cell;

use crate::{Rope, RopeError};

/// Controls how the `pos` argument of [`RopeCursor::seek`] is interpreted.
#[derive(Clone, Copy)]
pub enum SeekMode {
    /// `pos` is an absolute byte index from the start of the rope.
    Absolute,
    /// `pos` is relative to the current position.
    /// Positive = forward for [`ForwardCursor`], backward for [`BackwardCursor`].
    Relative,
    /// `pos` is counted back from the end: target = `len - pos`.
    RelativeToEnd,
    /// `pos` is a rope index relative to the start of the rope.
    Rope,
}

// ── shared state ──────────────────────────────────────────────────────────────

/// Per-cursor cache of the last accessed block, the current position, and the
/// base offset that defines the cursor's local coordinate system.
///
/// All fields are [`Cell`]-wrapped so they can be mutated through a shared
/// reference, enabling `&self` methods on cursors.
#[derive(Clone)]
pub struct CursorState<'a> {
    block_idx: Cell<usize>,
    block_start: Cell<usize>,
    block_end: Cell<usize>,
    block: Cell<&'a [Cell<u8>]>,
    initialized: Cell<bool>,
    current: Cell<Option<usize>>,
    /// Absolute rope index that maps to local position 0.
    /// All user-facing coordinates are relative to this value.
    offset: Cell<usize>,
}

impl<'a> CursorState<'a> {
    fn new() -> Self {
        Self::with_offset(0)
    }

    fn with_offset(offset: usize) -> Self {
        Self {
            block_idx: Cell::new(0),
            block_start: Cell::new(0),
            block_end: Cell::new(0),
            block: Cell::new(&[]),
            initialized: Cell::new(false),
            current: Cell::new(None),
            offset: Cell::new(offset),
        }
    }

    fn get(&self, rope: &'a Rope, i: usize) -> Option<u8> {
        if !self.initialized.get() || i < self.block_start.get() || i >= self.block_end.get() {
            let (bi, bs, be) = rope.lookup(i)?;
            self.block_idx.set(bi);
            self.block_start.set(bs);
            self.block_end.set(be);
            self.block.set(rope.get_block(bi)?);
            self.initialized.set(true);
        }
        Some(self.block.get()[i - self.block_start.get()].get())
    }

    fn set(&self, rope: &'a Rope, i: usize, value: u8) -> Result<(), RopeError> {
        if !self.initialized.get() || i < self.block_start.get() || i >= self.block_end.get() {
            let (bi, bs, be) = rope.lookup(i).ok_or(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} > {}",
                i,
                rope.len()
            )))?;
            self.block_idx.set(bi);
            self.block_start.set(bs);
            self.block_end.set(be);
            self.block
                .set(rope.get_block(bi).ok_or(RopeError::BlockNotFound(format!(
                    "Cannot find block for index {}",
                    i
                )))?);
            self.initialized.set(true);
        }
        self.block.get()[i - self.block_start.get()].set(value);
        Ok(())
    }
}

// ── trait ─────────────────────────────────────────────────────────────────────

/// Common interface for all rope cursors.
///
/// # Required methods
///
/// Implementors must provide [`rope`](RopeCursor::rope),
/// [`state`](RopeCursor::state), [`read_next`](RopeCursor::read_next) and
/// [`seek`](RopeCursor::seek). Everything else has a default implementation.
///
/// The direction of `read_next` and the sign convention for
/// [`SeekMode::Relative`] differ between [`ForwardCursor`] and
/// [`BackwardCursor`]; all other methods are identical.
pub trait RopeCursor<'a> {
    /// The rope this cursor is bound to.
    fn rope(&self) -> &'a Rope;
    /// Internal cache state — implementation detail exposed for default methods.
    fn state(&self) -> &CursorState<'a>;

    /// Read the next byte in cursor direction and advance the position.
    /// Returns `Err` at the exhausted end.
    fn read_next(&self) -> Result<u8, RopeError>;

    /// Move the cursor to a new position.
    ///
    /// `pos` is interpreted according to `mode`:
    /// - [`Absolute`](SeekMode::Absolute): local coordinate (`pos + offset` in the rope).
    /// - [`Rope`](SeekMode::Rope): raw rope index, ignores the offset.  Pass a value
    ///   from [`rope_tell`](RopeCursor::rope_tell) to restore a saved position.
    /// - [`Relative`](SeekMode::Relative): delta from the current position.
    ///   For [`ForwardCursor`], positive advances toward the end;
    ///   for [`BackwardCursor`], positive retreats toward the start.
    /// - [`RelativeToEnd`](SeekMode::RelativeToEnd): `rope.len() - pos`.
    ///
    /// Returns the new position as a **rope index** (same value as
    /// [`rope_tell`](RopeCursor::rope_tell) would return immediately after).
    fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError>;

    // ── default methods ───────────────────────────────────────────────────────

    /// Read the byte at **local** index `i` (relative to the cursor's offset)
    /// without moving the position.
    fn get(&self, i: usize) -> Option<u8> {
        self.state().get(self.rope(), i + self.state().offset.get())
    }

    /// Write `value` at **local** index `i` without moving the position.
    fn set(&self, i: usize, value: u8) -> Result<(), RopeError> {
        self.state()
            .set(self.rope(), i + self.state().offset.get(), value)
    }

    /// Current position relative to the cursor's offset, or `None` if the
    /// cursor has not moved yet.
    fn tell(&self) -> Option<usize> {
        let abs = self.state().current.get()?;
        Some(abs.saturating_sub(self.state().offset.get()))
    }

    /// Current position as an absolute rope index.
    ///
    /// Unlike [`tell`](RopeCursor::tell), this method **always** returns a
    /// value: if the cursor has not moved yet, it returns the cursor's offset
    /// (the rope index of local position 0).
    ///
    /// Use the returned value with [`SeekMode::Rope`] to restore a position,
    /// or as a truncation point after a write pass.
    fn rope_tell(&self) -> usize {
        self.state()
            .current
            .get()
            .unwrap_or(self.state().offset.get())
    }

    /// Number of bytes visible through this cursor (`rope.len() - offset`).
    fn len(&self) -> usize {
        self.rope().len().saturating_sub(self.state().offset.get())
    }

    /// Reset the cursor to its initial state (positioned before the first
    /// byte of its local view).  Equivalent to `seek(0, Absolute)` on a
    /// fresh cursor, but works even when `current` is `None`.
    fn reset(&self) {
        self.state().current.set(None);
    }

    /// Read the byte at the current position without advancing.
    fn peek(&self) -> Option<u8> {
        self.state().get(self.rope(), self.state().current.get()?)
    }

    /// Write `value` at the current position without advancing.
    fn poke(&self, value: u8) -> Result<(), RopeError> {
        let pos = self.state().current.get().ok_or(RopeError::CurrentNotSet)?;
        self.state().set(self.rope(), pos, value)
    }

    /// Move backward by `go_back_of` steps (toward lower indices for
    /// [`ForwardCursor`], toward higher indices for [`BackwardCursor`]).
    fn rewind(&self, go_back_of: usize) -> Result<(), RopeError> {
        self.seek(-(go_back_of as isize), SeekMode::Relative)?;
        Ok(())
    }

    /// Move forward by `ahead` steps (opposite of [`rewind`](RopeCursor::rewind)).
    fn forward(&self, ahead: usize) -> Result<(), RopeError> {
        self.seek(ahead as isize, SeekMode::Relative)?;
        Ok(())
    }
}

// ── ForwardCursor ─────────────────────────────────────────────────────────────

/// A cursor that reads from the start toward the end of the rope.
///
/// - `read_next`: first call reads index 0, then 1, 2, …
/// - `seek(Relative, +n)`: advances by n.
/// - `rewind(n)`: steps back by n.
///
/// Extra methods not in the trait: [`read_ahead`](ForwardCursor::read_ahead),
/// [`write`](ForwardCursor::write), [`iter`](ForwardCursor::iter).
#[derive(Clone)]
pub struct ForwardCursor<'a> {
    rope: &'a Rope,
    state: CursorState<'a>,
}

impl<'a> ForwardCursor<'a> {
    /// Create a new forward cursor positioned before the first byte.
    pub fn new(rope: &'a Rope) -> Self {
        Self {
            rope,
            state: CursorState::new(),
        }
    }

    /// Read the byte at `current + ahead` without moving the position.
    pub fn read_ahead(&self, ahead: usize) -> Result<u8, RopeError> {
        let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
        self.state
            .get(self.rope, pos + ahead)
            .ok_or(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} + {} > {}",
                pos,
                ahead,
                self.rope.len()
            )))
    }

    /// Write `value` at the current position and advance by one.
    ///
    /// If the cursor has not moved yet, writes at the first byte of its local
    /// view (absolute index = offset).
    pub fn write(&self, value: u8) -> Result<(), RopeError> {
        let pos = self.state.current.get().unwrap_or(self.state.offset.get());
        self.state.set(self.rope, pos, value)?;
        self.state.current.set(Some(pos + 1));
        Ok(())
    }

    /// Return a shared-borrow iterator that yields bytes forward.
    ///
    /// Because the iterator holds `&self` rather than `&mut self`, methods
    /// such as [`tell`](RopeCursor::tell) and [`seek`](RopeCursor::seek) can
    /// be called on the cursor inside the loop body.
    pub fn iter(&self) -> ForwardIter<'a, '_> {
        ForwardIter { cursor: self }
    }

    /// Create a new [`ForwardCursor`] whose local position 0 starts at the
    /// current absolute position of `self`.
    ///
    /// The new cursor shares the same underlying [`Rope`] (with the same
    /// [`Cell`]-based interior mutability) but has an independent position and
    /// an `offset` equal to `self.absolute_tell()`.  If `self` has not moved
    /// yet, the new cursor starts at the same offset as `self`.
    pub fn cursor(&self) -> ForwardCursor<'a> {
        let new_offset = self.rope_tell();
        ForwardCursor {
            rope: self.rope,
            state: CursorState::with_offset(new_offset),
        }
    }
}

impl<'a> RopeCursor<'a> for ForwardCursor<'a> {
    fn rope(&self) -> &'a Rope {
        self.rope
    }
    fn state(&self) -> &CursorState<'a> {
        &self.state
    }

    fn read_next(&self) -> Result<u8, RopeError> {
        let next_pos = match self.state.current.get() {
            Some(i) => i + 1,
            None => self.state.offset.get(),
        };
        let value = self
            .state
            .get(self.rope, next_pos)
            .ok_or(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} > {}",
                next_pos,
                self.rope.len()
            )))?;
        self.state.current.set(Some(next_pos));
        Ok(value)
    }

    fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError> {
        let offset = self.state.offset.get() as isize;
        let abs_pos = match mode {
            SeekMode::Absolute => pos + offset,
            SeekMode::Relative => {
                self.state.current.get().ok_or(RopeError::CurrentNotSet)? as isize + pos
            }
            SeekMode::RelativeToEnd => self.rope.len() as isize - pos,
            SeekMode::Rope => pos,
        };
        if abs_pos < 0 {
            return Err(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} < 0",
                abs_pos
            )));
        }
        self.state.current.set(Some(abs_pos as usize));
        Ok(abs_pos as usize)
    }
}

impl Iterator for ForwardCursor<'_> {
    type Item = u8;
    fn next(&mut self) -> Option<Self::Item> {
        self.read_next().ok()
    }
}

/// Shared-borrow iterator returned by [`ForwardCursor::iter`].
pub struct ForwardIter<'a, 'b> {
    cursor: &'b ForwardCursor<'a>,
}

impl Iterator for ForwardIter<'_, '_> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.cursor.read_next().ok()
    }
}

// ── BackwardCursor ────────────────────────────────────────────────────────────

/// A cursor that reads from the end toward the start of the rope.
///
/// - `read_next`: first call reads index `len-1`, then `len-2`, …
/// - `seek(Relative, +n)`: retreats by n (subtracts n from the index).
/// - `rewind(n)`: advances toward the end by n.
///
/// Extra methods not in the trait: [`read_behind`](BackwardCursor::read_behind),
/// [`iter`](BackwardCursor::iter).
#[derive(Clone)]
pub struct BackwardCursor<'a> {
    rope: &'a Rope,
    state: CursorState<'a>,
}

impl<'a> BackwardCursor<'a> {
    /// Create a new backward cursor positioned past the last byte.
    pub fn new(rope: &'a Rope) -> Self {
        Self {
            rope,
            state: CursorState::new(),
        }
    }

    /// Read the byte at `current + behind` (toward higher indices) without moving.
    pub fn read_behind(&self, behind: usize) -> Result<u8, RopeError> {
        let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
        let target = pos
            .checked_add(behind)
            .filter(|&t| t < self.rope.len())
            .ok_or(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} + {} > {}",
                pos,
                behind,
                self.rope.len()
            )))?;
        self.state
            .get(self.rope, target)
            .ok_or(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} + {} > {}",
                pos,
                behind,
                self.rope.len()
            )))
    }

    /// Return a shared-borrow iterator that yields bytes backward.
    ///
    /// Because the iterator holds `&self` rather than `&mut self`, methods
    /// such as [`tell`](RopeCursor::tell) and [`seek`](RopeCursor::seek) can
    /// be called on the cursor inside the loop body.
    pub fn iter(&self) -> BackwardIter<'a, '_> {
        BackwardIter { cursor: self }
    }

    /// Create a new [`BackwardCursor`] that stops at the current absolute
    /// position of `self` (used as the lower bound / offset of the new cursor).
    ///
    /// The new cursor scans from `rope.len() - 1` down to the current absolute
    /// position of `self`.  If `self` has not moved yet, the new cursor has the
    /// same offset as `self` (no restriction).
    pub fn cursor(&self) -> BackwardCursor<'a> {
        let new_offset = self.rope_tell();
        BackwardCursor {
            rope: self.rope,
            state: CursorState::with_offset(new_offset),
        }
    }
}

impl<'a> RopeCursor<'a> for BackwardCursor<'a> {
    fn rope(&self) -> &'a Rope {
        self.rope
    }
    fn state(&self) -> &CursorState<'a> {
        &self.state
    }

    fn read_next(&self) -> Result<u8, RopeError> {
        let offset = self.state.offset.get();
        let next_pos = match self.state.current.get() {
            None => self
                .rope
                .len()
                .checked_sub(1)
                .ok_or(RopeError::OutOfBounds(
                    "BackwardCursor: rope is empty".to_string(),
                ))?,
            Some(i) if i <= offset => {
                return Err(RopeError::OutOfBounds(
                    "BackwardCursor: already at beginning".to_string(),
                ));
            }
            Some(i) => i - 1,
        };
        let value = self
            .state
            .get(self.rope, next_pos)
            .ok_or(RopeError::OutOfBounds(format!(
                "BackwardCursor: index out of bounds at i={}",
                next_pos
            )))?;
        self.state.current.set(Some(next_pos));
        Ok(value)
    }

    fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError> {
        let offset = self.state.offset.get() as isize;
        let abs_pos = match mode {
            SeekMode::Absolute => pos + offset,
            SeekMode::Relative => {
                self.state.current.get().ok_or(RopeError::CurrentNotSet)? as isize - pos
            }
            SeekMode::RelativeToEnd => self.rope.len() as isize - pos,
            SeekMode::Rope => pos,
        };
        if abs_pos < 0 {
            return Err(RopeError::OutOfBounds(format!(
                "index out of bounds: i={} < 0",
                abs_pos
            )));
        }
        self.state.current.set(Some(abs_pos as usize));
        Ok(abs_pos as usize)
    }
}

impl Iterator for BackwardCursor<'_> {
    type Item = u8;
    fn next(&mut self) -> Option<Self::Item> {
        self.read_next().ok()
    }
}

/// Shared-borrow iterator returned by [`BackwardCursor::iter`].
pub struct BackwardIter<'a, 'b> {
    cursor: &'b BackwardCursor<'a>,
}

impl Iterator for BackwardIter<'_, '_> {
    type Item = u8;
    fn next(&mut self) -> Option<u8> {
        self.cursor.read_next().ok()
    }
}

// ── tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Rope;

    fn rope(data: &[u8]) -> Rope {
        let mut r = Rope::new(None);
        r.push(data.to_vec());
        r
    }

    fn rope2(a: &[u8], b: &[u8]) -> Rope {
        let mut r = Rope::new(None);
        r.push(a.to_vec());
        r.push(b.to_vec());
        r
    }

    // ── ForwardCursor ─────────────────────────────────────────────────────────

    #[test]
    fn forward_reads_all_bytes() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        let out: Vec<u8> = c.collect();
        assert_eq!(out, b"ACGT");
    }

    #[test]
    fn forward_tell_tracks_position() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        assert_eq!(c.tell(), None);
        c.read_next().unwrap();
        assert_eq!(c.tell(), Some(0));
        c.read_next().unwrap();
        assert_eq!(c.tell(), Some(1));
    }

    #[test]
    fn forward_iter_with_tell_inside_loop() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        let mut positions = Vec::new();
        for _ in c.iter() {
            positions.push(c.tell());
        }
        assert_eq!(positions, vec![Some(0), Some(1), Some(2), Some(3)]);
    }

    #[test]
    fn forward_read_ahead() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // at 0 = 'A'
        assert_eq!(c.read_ahead(1).unwrap(), b'C');
        assert_eq!(c.read_ahead(2).unwrap(), b'G');
        assert_eq!(c.tell(), Some(0)); // position unchanged
    }

    #[test]
    fn forward_write_and_read_back() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        c.write(b'X').unwrap();
        c.write(b'Y').unwrap();
        let c2 = r.fw_cursor();
        assert_eq!(c2.read_next().unwrap(), b'X');
        assert_eq!(c2.read_next().unwrap(), b'Y');
        assert_eq!(c2.read_next().unwrap(), b'G');
    }

    #[test]
    fn forward_rewind_and_reread() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // A  → current = Some(0)
        c.read_next().unwrap(); // C  → current = Some(1)
        c.read_next().unwrap(); // G  → current = Some(2)
        c.rewind(1).unwrap(); // current = Some(1) → next read = index 2
        assert_eq!(c.read_next().unwrap(), b'G');
    }

    #[test]
    fn forward_seek_absolute() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        c.seek(2, SeekMode::Absolute).unwrap();
        assert_eq!(c.read_next().unwrap(), b'T');
    }

    #[test]
    fn forward_seek_relative_to_end() {
        let r = rope(b"ACGT");
        // seek(1, RelativeToEnd): current = len-1 = 3; peek() reads index 3 = T.
        let c = r.fw_cursor();
        c.seek(1, SeekMode::RelativeToEnd).unwrap();
        assert_eq!(c.peek().unwrap(), b'T');
        // seek(2, RelativeToEnd): current = len-2 = 2; read_next reads index 3 = T.
        let c2 = r.fw_cursor();
        c2.seek(2, SeekMode::RelativeToEnd).unwrap();
        assert_eq!(c2.read_next().unwrap(), b'T');
    }

    #[test]
    fn forward_get_random_access() {
        let r = rope(b"ACGT");
        let c = r.fw_cursor();
        assert_eq!(c.get(0), Some(b'A'));
        assert_eq!(c.get(3), Some(b'T'));
        assert_eq!(c.get(4), None);
    }

    #[test]
    fn forward_crosses_block_boundary() {
        let r = rope2(b"AC", b"GT");
        let c = r.fw_cursor();
        let out: Vec<u8> = c.collect();
        assert_eq!(out, b"ACGT");
    }

    // ── BackwardCursor ────────────────────────────────────────────────────────

    #[test]
    fn backward_reads_all_bytes_in_reverse() {
        let r = rope(b"ACGT");
        let c = r.bw_cursor();
        let out: Vec<u8> = c.collect();
        assert_eq!(out, b"TGCA");
    }

    #[test]
    fn backward_tell_tracks_position() {
        let r = rope(b"ACGT");
        let c = r.bw_cursor();
        assert_eq!(c.tell(), None);
        c.read_next().unwrap(); // reads index 3
        assert_eq!(c.tell(), Some(3));
        c.read_next().unwrap(); // reads index 2
        assert_eq!(c.tell(), Some(2));
    }

    #[test]
    fn backward_iter_with_tell_and_seek_inside_loop() {
        let r = rope(b"ACGT");
        let c = r.bw_cursor();
        let mut restart: usize = 0;
        for byte in c.iter() {
            if byte == b'G' {
                restart = c.tell().unwrap();
            }
            if byte == b'A' {
                // seek back to G and break
                c.seek(restart as isize, SeekMode::Absolute).ok();
                break;
            }
        }
        assert_eq!(c.tell(), Some(restart));
    }

    #[test]
    fn backward_rewind_moves_toward_end() {
        let r = rope(b"ACGT");
        let c = r.bw_cursor();
        c.read_next().unwrap(); // index 3 = T
        c.read_next().unwrap(); // index 2 = G
        c.rewind(1).unwrap(); // back to index 3
        assert_eq!(c.tell(), Some(3));
        assert_eq!(c.read_next().unwrap(), b'G'); // reads index 2
    }

    #[test]
    fn backward_crosses_block_boundary() {
        let r = rope2(b"AC", b"GT");
        let c = r.bw_cursor();
        let out: Vec<u8> = c.collect();
        assert_eq!(out, b"TGCA");
    }

    #[test]
    fn backward_empty_rope_returns_error() {
        let r = Rope::new(None);
        let c = r.bw_cursor();
        assert!(c.read_next().is_err());
    }

    #[test]
    fn forward_empty_rope_returns_error() {
        let r = Rope::new(None);
        let c = r.fw_cursor();
        assert!(c.read_next().is_err());
    }

    // ── offset / sub-cursor ───────────────────────────────────────────────────

    #[test]
    fn forward_cursor_reads_from_offset() {
        // cursor() at current=Some(2) → new cursor reads from index 2
        let r = rope(b"ABCDE");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // A → current=Some(0)
        c.read_next().unwrap(); // B → current=Some(1)
        c.read_next().unwrap(); // C → current=Some(2)
        let sub = c.cursor(); // offset=2 (absolute_tell=2)
        assert_eq!(sub.read_next().unwrap(), b'C'); // reads index 2
        assert_eq!(sub.tell(), Some(0)); // relative: 2-2=0
        assert_eq!(sub.rope_tell(), 2);
        assert_eq!(sub.read_next().unwrap(), b'D');
        assert_eq!(sub.tell(), Some(1)); // relative: 3-2=1
    }

    #[test]
    fn forward_cursor_get_uses_relative_index() {
        let r = rope(b"ABCDE");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // A → current=Some(0), absolute_tell=0
        let _sub = c.cursor(); // offset=0 — created to show cursor() compiles; not used further
        // From sub2 with offset=2: get(0)=C, get(2)=E
        let c2 = r.fw_cursor();
        c2.read_next().unwrap(); // at 0
        c2.read_next().unwrap(); // at 1
        c2.read_next().unwrap(); // at 2, absolute=2
        let sub2 = c2.cursor(); // offset=2
        assert_eq!(sub2.get(0), Some(b'C')); // local 0 = absolute 2
        assert_eq!(sub2.get(2), Some(b'E')); // local 2 = absolute 4
        assert_eq!(sub2.get(3), None); // local 3 = absolute 5, OOB
    }

    #[test]
    fn forward_cursor_len_reflects_offset() {
        let r = rope(b"ABCDE"); // len=5
        let c = r.fw_cursor();
        c.read_next().unwrap();
        c.read_next().unwrap();
        c.read_next().unwrap(); // absolute_tell=2
        let sub = c.cursor(); // offset=2
        assert_eq!(sub.len(), 3); // 5 - 2
    }

    #[test]
    fn forward_reset_goes_back_to_start() {
        let r = rope(b"ABCDE");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // A
        c.read_next().unwrap(); // B
        c.reset();
        assert_eq!(c.tell(), None);
        assert_eq!(c.read_next().unwrap(), b'A'); // starts over
    }

    #[test]
    fn forward_sub_cursor_write_and_reset() {
        // Write two bytes, discard them via reset(), write again.
        let r = rope(b"XXXXX");
        let c = r.fw_cursor();
        c.write(b'A').unwrap(); // absolute 0 → current=Some(1)
        c.write(b'B').unwrap(); // absolute 1 → current=Some(2)
        let seg = c.cursor(); // absolute_tell=2, offset=2
        seg.write(b'C').unwrap(); // absolute 2 → current=Some(3), tell=3-2=1
        seg.write(b'D').unwrap(); // absolute 3 → current=Some(4), tell=4-2=2
        assert_eq!(seg.tell(), Some(2)); // 2 bytes written into this segment
        seg.reset();
        assert_eq!(seg.tell(), None);
        seg.write(b'E').unwrap(); // absolute 2 again
        let all: Vec<u8> = r.fw_cursor().collect();
        assert_eq!(&all[..3], b"ABE");
    }

    #[test]
    fn backward_cursor_stops_at_offset() {
        // BackwardCursor.cursor() creates a cursor with offset = absolute_tell.
        // offset = local position 0 (inclusive lower bound).
        // The cursor reads rope.len()-1 downto offset, then stops.
        let r = rope(b"ABCDE"); // 0=A 1=B 2=C 3=D 4=E
        let bw = r.bw_cursor();
        bw.read_next().unwrap(); // E=4, current=Some(4)
        bw.read_next().unwrap(); // D=3, current=Some(3), absolute_tell=3
        // sub: offset=3, reads from 4 down to 3 (inclusive), then stops.
        let sub = bw.cursor();
        assert_eq!(sub.read_next().unwrap(), b'E'); // index 4, tell=4-3=1
        assert_eq!(sub.read_next().unwrap(), b'D'); // index 3, tell=3-3=0 (local 0)
        assert!(sub.read_next().is_err()); // would go to 2 < offset=3
    }

    #[test]
    fn forward_absolute_tell_unchanged_by_offset() {
        let r = rope(b"ABCDE");
        let c = r.fw_cursor();
        c.read_next().unwrap(); // absolute=0
        let sub = c.cursor(); // offset=0
        sub.read_next().unwrap(); // reads index 0, absolute_tell=0
        sub.read_next().unwrap(); // reads index 1, absolute_tell=1
        assert_eq!(sub.tell(), Some(1));
        assert_eq!(sub.rope_tell(), 1);
        // sub2 with offset=1
        let sub2 = sub.cursor(); // offset=1
        sub2.read_next().unwrap(); // reads index 1, absolute=1
        assert_eq!(sub2.tell(), Some(0)); // relative: 1-1=0
        assert_eq!(sub2.rope_tell(), 1);
    }
}
