//! The [`Rope`] type: a segmented, in-place-mutable byte sequence.
//!
//! A `Rope` is a sequence of byte blocks (slices) stored contiguously in a
//! `Vec<Vec<Cell<u8>>>`. Blocks are never merged or reallocated; bytes within
//! a block can be modified through a [`ForwardCursor`] while another cursor
//! reads ahead — the [`Cell<u8>`][std::cell::Cell] wrapper provides the
//! required interior mutability without `unsafe` at the call site.
//!
//! ## Core operations
//!
//! | Method | Description |
//! |---|---|
//! | [`push`][Rope::push] | Append a `Vec<u8>` block |
//! | [`split_off`][Rope::split_off] | Split the rope at a byte offset |
//! | [`fw_cursor`][Rope::fw_cursor] | Forward cursor (read/write left→right) |
//! | [`bw_cursor`][Rope::bw_cursor] | Backward cursor (read right→left) |
//!
//! ## Block indexing
//!
//! `start_block_idx[i]` holds the absolute byte offset of the first byte of
//! block `i`. [`lookup`][Rope::lookup] binary-searches this index to resolve
//! an absolute offset to `(block_idx, block_start, block_end)` in O(log n).

use crate::{BackwardCursor, ForwardCursor, RopeError};
use std::cell::Cell;

/// A segmented, in-place-mutable byte sequence.
///
/// See the [module-level documentation][crate::rope] for a full overview.
pub struct Rope {
    pub(crate) mime_type: Option<&'static str>,
    pub(crate) blocks: Vec<Vec<Cell<u8>>>,
    pub(crate) length: usize,
    pub(crate) start_block_idx: Vec<usize>,
}

impl Rope {
    /// Create an empty rope (no allocations).
    pub fn new(mime_type: Option<&'static str>) -> Self {
        Self {
            mime_type,
            blocks: Vec::new(),
            length: 0,
            start_block_idx: Vec::new(),
        }
    }

    /// Append a block of bytes to the rope.
    ///
    /// The `Vec<u8>` is reinterpreted as `Vec<Cell<u8>>` in place (zero-copy)
    /// using the guaranteed identical memory layout of `Cell<T>` and `T`.
    pub fn push(&mut self, block: Vec<u8>) {
        let block_len = block.len();
        self.start_block_idx.push(self.length);
        // Cell<u8> has the same memory layout as u8 (language guarantee).
        let cell_block: Vec<Cell<u8>> = unsafe {
            let mut v = std::mem::ManuallyDrop::new(block);
            Vec::from_raw_parts(v.as_mut_ptr() as *mut Cell<u8>, v.len(), v.capacity())
        };
        self.blocks.push(cell_block);
        self.length += block_len;
    }

    /// The MIME type of the rope, if known.
    pub fn mime_type(&self) -> Option<&str> {
        self.mime_type.as_deref()
    }

    /// Total number of blocks.
    pub fn n_blocks(&self) -> usize {
        self.blocks.len()
    }

    /// Return the slice of `Cell<u8>` for block `block_idx`, or `None` if out
    /// of range.
    pub(crate) fn get_block(&self, block_idx: usize) -> Option<&[Cell<u8>]> {
        self.blocks.get(block_idx).map(Vec::as_slice)
    }

    /// Total byte length across all blocks.
    pub fn len(&self) -> usize {
        self.length
    }

    /// `true` if the rope contains no bytes.
    pub fn is_empty(&self) -> bool {
        self.blocks.is_empty()
    }

    /// Resolve absolute byte offset `i` to `(block_idx, block_start, block_end)`.
    ///
    /// Returns `None` when `i >= self.length` or the rope is empty.
    /// `block_start` and `block_end` are absolute byte offsets of the first and
    /// one-past-last byte of the block, respectively.
    pub(crate) fn lookup(&self, i: usize) -> Option<(usize, usize, usize)> {
        if i >= self.length || self.blocks.is_empty() {
            return None;
        }
        let block_idx = self.start_block_idx.partition_point(|&s| s <= i) - 1;
        let from = self.start_block_idx[block_idx];
        let to = if block_idx + 1 < self.blocks.len() {
            self.start_block_idx[block_idx + 1]
        } else {
            self.length
        };
        Some((block_idx, from, to))
    }

    /// Split the rope at byte offset `pos`.
    ///
    /// `self` retains bytes `[0, pos)` and returns a new rope with bytes
    /// `[pos, len)`.  If `pos` falls inside a block, that block is split in
    /// two.
    ///
    /// Returns `Err` if `pos > self.length`.
    pub fn split_off(&mut self, pos: usize) -> Result<Rope, RopeError> {
        if pos > self.length {
            return Err(RopeError::OutOfBounds(format!(
                "split_at: pos={} > length={}",
                pos, self.length
            )));
        }

        if pos == self.length {
            return Ok(Rope::new(self.mime_type.clone()));
        }

        let (block_idx, from, _) = self.lookup(pos).ok_or_else(|| {
            RopeError::OutOfBounds(format!("split_at: lookup failed at pos={}", pos))
        })?;
        let cut_offset = pos - from;

        let mut tail_blocks = self.blocks.split_off(block_idx + 1);
        self.start_block_idx.truncate(block_idx + 1);

        let tail_part = self.blocks[block_idx].split_off(cut_offset);
        if !tail_part.is_empty() {
            tail_blocks.insert(0, tail_part);
        }
        // If the cut was exactly at the start of this block, it is now empty — discard it.
        if self.blocks[block_idx].is_empty() {
            self.blocks.pop();
            self.start_block_idx.pop();
        }

        let mut tail_length = 0;
        let tail_starts: Vec<usize> = tail_blocks
            .iter()
            .map(|b| {
                let s = tail_length;
                tail_length += b.len();
                s
            })
            .collect();

        self.length = pos;

        Ok(Rope {
            mime_type: self.mime_type.clone(),
            blocks: tail_blocks,
            length: tail_length,
            start_block_idx: tail_starts,
        })
    }

    /// Discard all bytes at and after `pos`, keeping `[0, pos)`.
    ///
    /// Equivalent to `split_off(pos)` with the tail dropped.
    /// Returns `Err` if `pos > self.length`.
    pub fn truncate(&mut self, pos: usize) -> Result<(), RopeError> {
        self.split_off(pos).map(|_| ())
    }

    /// Create a forward cursor positioned before the first byte.
    pub fn fw_cursor(&self) -> ForwardCursor<'_> {
        ForwardCursor::new(self)
    }

    /// Create a backward cursor positioned after the last byte.
    pub fn bw_cursor(&self) -> BackwardCursor<'_> {
        BackwardCursor::new(self)
    }
}

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

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

    fn flat(r: &Rope) -> Vec<u8> {
        r.fw_cursor().collect()
    }

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

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

    // ── basic properties ──────────────────────────────────────────────────────

    #[test]
    fn empty_rope_is_empty() {
        let r = Rope::new(None);
        assert!(r.is_empty());
        assert_eq!(r.len(), 0);
        assert_eq!(r.n_blocks(), 0);
    }

    #[test]
    fn single_push_len_and_n_blocks() {
        let r = make(b"hello");
        assert!(!r.is_empty());
        assert_eq!(r.len(), 5);
        assert_eq!(r.n_blocks(), 1);
        assert_eq!(r.mime_type(), None);
    }

    #[test]
    fn two_pushes_len_accumulates() {
        let r = make2(b"abc", b"de");
        assert_eq!(r.len(), 5);
        assert_eq!(r.n_blocks(), 2);
        assert_eq!(r.mime_type(), None);
    }

    #[test]
    fn flat_read_matches_input() {
        assert_eq!(flat(&make(b"ACGT")), b"ACGT");
    }

    #[test]
    fn flat_read_two_blocks_concatenated() {
        assert_eq!(flat(&make2(b"ACG", b"T")), b"ACGT");
    }

    // ── lookup ────────────────────────────────────────────────────────────────

    #[test]
    fn lookup_first_byte() {
        let r = make(b"ABCD");
        let (bi, from, to) = r.lookup(0).unwrap();
        assert_eq!(bi, 0);
        assert_eq!(from, 0);
        assert_eq!(to, 4);
    }

    #[test]
    fn lookup_last_byte() {
        let r = make(b"ABCD");
        let (bi, from, to) = r.lookup(3).unwrap();
        assert_eq!(bi, 0);
        assert_eq!(from, 0);
        assert_eq!(to, 4);
    }

    #[test]
    fn lookup_out_of_bounds_returns_none() {
        let r = make(b"AB");
        assert!(r.lookup(2).is_none());
        assert!(r.lookup(99).is_none());
    }

    #[test]
    fn lookup_empty_rope_returns_none() {
        assert!(Rope::new(None).lookup(0).is_none());
    }

    #[test]
    fn lookup_second_block_first_byte() {
        let r = make2(b"ABC", b"DE");
        let (bi, from, to) = r.lookup(3).unwrap();
        assert_eq!(bi, 1);
        assert_eq!(from, 3);
        assert_eq!(to, 5);
    }

    #[test]
    fn lookup_second_block_last_byte() {
        let r = make2(b"ABC", b"DE");
        let (bi, from, to) = r.lookup(4).unwrap();
        assert_eq!(bi, 1);
        assert_eq!(from, 3);
        assert_eq!(to, 5);
    }

    // ── get_block ─────────────────────────────────────────────────────────────

    #[test]
    fn get_block_returns_correct_slice() {
        let r = make2(b"ABC", b"DE");
        let b0: Vec<u8> = r.get_block(0).unwrap().iter().map(|c| c.get()).collect();
        let b1: Vec<u8> = r.get_block(1).unwrap().iter().map(|c| c.get()).collect();
        assert_eq!(b0, b"ABC");
        assert_eq!(b1, b"DE");
    }

    #[test]
    fn get_block_out_of_range_returns_none() {
        let r = make(b"X");
        assert!(r.get_block(1).is_none());
    }

    // ── split_off ─────────────────────────────────────────────────────────────

    #[test]
    fn split_off_at_zero_head_empty_tail_all() {
        let mut r = make(b"ABCDE");
        let tail = r.split_off(0).unwrap();
        assert_eq!(r.len(), 0);
        assert_eq!(flat(&tail), b"ABCDE");
    }

    #[test]
    fn split_off_at_len_tail_empty_head_all() {
        let mut r = make(b"ABCDE");
        let tail = r.split_off(5).unwrap();
        assert_eq!(flat(&r), b"ABCDE");
        assert_eq!(tail.len(), 0);
        assert!(tail.is_empty());
    }

    #[test]
    fn split_off_in_middle_of_block() {
        let mut r = make(b"ABCDE");
        let tail = r.split_off(2).unwrap();
        assert_eq!(flat(&r), b"AB");
        assert_eq!(flat(&tail), b"CDE");
    }

    #[test]
    fn split_off_at_block_boundary() {
        let mut r = make2(b"ABC", b"DE");
        let tail = r.split_off(3).unwrap();
        assert_eq!(flat(&r), b"ABC");
        assert_eq!(flat(&tail), b"DE");
    }

    #[test]
    fn split_off_inside_second_block() {
        let mut r = make2(b"ABC", b"DE");
        let tail = r.split_off(4).unwrap();
        assert_eq!(flat(&r), b"ABCD");
        assert_eq!(flat(&tail), b"E");
    }

    #[test]
    fn split_off_out_of_bounds_returns_err() {
        let mut r = make(b"AB");
        assert!(r.split_off(3).is_err());
    }

    #[test]
    fn split_off_preserves_n_blocks_head() {
        let mut r = make2(b"ABCDE", b"FGHIJ");
        r.split_off(5).unwrap();
        assert_eq!(r.n_blocks(), 1);
        assert_eq!(flat(&r), b"ABCDE");
    }
}
