use std::cell::{Cell, RefCell};
use std::cmp::min;
use std::collections::BTreeSet;
use std::fmt;
use std::rc::Rc;

use ruin_reactivity::{Event, Reactor, Source, event_in, source_in};

use crate::delta::{EditDelta, RopeEdit, TextRange};
use crate::metrics::{TextMetrics, TextPoint};

pub const DEFAULT_LEAF_TARGET_BYTES: usize = 1024;
const DEFAULT_LEAF_MAX_BYTES: usize = DEFAULT_LEAF_TARGET_BYTES * 2;

#[derive(Clone)]
pub struct ReactiveRope {
    inner: Rc<RopeInner>,
}

pub struct RopeBuilder {
    reactor: Reactor,
    leaf_target_bytes: usize,
    chunks: Vec<String>,
}

#[derive(Clone)]
pub struct RopeChunk {
    text: Rc<str>,
    pub start: TextPoint,
    pub metrics: TextMetrics,
}

#[derive(Clone)]
pub struct RopeCursor {
    rope: ReactiveRope,
    next_offset: usize,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RopeSnapshotMetrics {
    pub leaves: usize,
    pub text: TextMetrics,
    pub revision: u64,
}

struct RopeInner {
    reactor: Reactor,
    structure: Source,
    revision: Cell<u64>,
    leaf_target_bytes: usize,
    leaves: RefCell<Vec<Rc<Leaf>>>,
    edit_events: Event<EditDelta>,
}

struct Leaf {
    source: Source,
    text: RefCell<Rc<str>>,
    metrics: Cell<TextMetrics>,
    tail_chars: Cell<usize>,
}

impl RopeBuilder {
    pub fn new(reactor: &Reactor) -> Self {
        Self {
            reactor: reactor.clone(),
            leaf_target_bytes: DEFAULT_LEAF_TARGET_BYTES,
            chunks: Vec::new(),
        }
    }

    pub fn leaf_target_bytes(mut self, bytes: usize) -> Self {
        self.leaf_target_bytes = bytes.max(32);
        self
    }

    pub fn push(mut self, text: impl Into<String>) -> Self {
        self.chunks.push(text.into());
        self
    }

    pub fn build(self) -> ReactiveRope {
        ReactiveRope::from_chunks(self.reactor, self.leaf_target_bytes, self.chunks)
    }
}

impl ReactiveRope {
    pub fn new(reactor: &Reactor) -> Self {
        RopeBuilder::new(reactor).build()
    }

    pub fn from_text(reactor: &Reactor, text: impl AsRef<str>) -> Self {
        RopeBuilder::new(reactor).push(text.as_ref()).build()
    }

    fn from_chunks(reactor: Reactor, leaf_target_bytes: usize, chunks: Vec<String>) -> Self {
        let structure = source_in(&reactor);
        let edit_events = event_in(&reactor);
        let mut leaves = Vec::new();
        for chunk in chunks {
            Self::append_chunk_into(&reactor, leaf_target_bytes, &mut leaves, &chunk);
        }
        if leaves.is_empty() {
            leaves.push(Rc::new(Leaf::new(&reactor, String::new())));
        }

        Self {
            inner: Rc::new(RopeInner {
                reactor,
                structure,
                revision: Cell::new(0),
                leaf_target_bytes,
                leaves: RefCell::new(leaves),
                edit_events,
            }),
        }
    }

    pub fn edit_events(&self) -> Event<EditDelta> {
        self.inner.edit_events.clone()
    }

    pub fn reactor(&self) -> Reactor {
        self.inner.reactor.clone()
    }

    pub fn snapshot_metrics(&self) -> RopeSnapshotMetrics {
        self.inner.structure.observe();
        RopeSnapshotMetrics {
            leaves: self.inner.leaves.borrow().len(),
            text: self.total_metrics(),
            revision: self.inner.revision.get(),
        }
    }

    pub fn len_bytes(&self) -> usize {
        self.total_metrics().bytes
    }

    pub fn is_empty(&self) -> bool {
        self.len_bytes() == 0
    }

    pub fn point_at(&self, offset: usize) -> TextPoint {
        self.inner.structure.observe();
        let offset = offset.min(self.len_bytes());
        let mut point = TextPoint::origin();
        let leaves = self.inner.leaves.borrow();
        let mut remaining = offset;
        for leaf in &*leaves {
            let bytes = leaf.metrics.get().bytes;
            if remaining > bytes {
                remaining -= bytes;
                point.byte += bytes;
                point.char_offset += leaf.metrics.get().chars;
                point.utf16_offset += leaf.metrics.get().utf16;
                point.line += leaf.metrics.get().line_breaks;
                if leaf.metrics.get().line_breaks == 0 {
                    point.column += leaf.tail_chars.get();
                } else {
                    point.column = leaf.tail_chars.get();
                }
                continue;
            }
            let text = leaf.read();
            point = point.advance(&text[..remaining]);
            return point;
        }
        point
    }

    pub fn slice(&self, range: TextRange) -> String {
        self.inner.structure.observe();
        self.assert_char_boundary(range.start);
        self.assert_char_boundary(range.end);
        assert!(range.start <= range.end, "invalid text range");
        assert!(range.end <= self.len_bytes(), "range out of bounds");

        let mut out = String::with_capacity(range.len());
        let mut cursor = 0usize;
        let leaves = self.inner.leaves.borrow();
        for leaf in &*leaves {
            let metrics = leaf.metrics.get();
            let end = cursor + metrics.bytes;
            if end <= range.start {
                cursor = end;
                continue;
            }
            if cursor >= range.end {
                break;
            }
            let text = leaf.read();
            let start_in_leaf = range.start.saturating_sub(cursor);
            let end_in_leaf = min(metrics.bytes, range.end - cursor);
            out.push_str(&text[start_in_leaf..end_in_leaf]);
            cursor = end;
        }
        out
    }

    pub fn chunk_at(&self, offset: usize) -> Option<RopeChunk> {
        self.inner.structure.observe();
        if offset >= self.len_bytes() {
            return None;
        }
        let mut start = TextPoint::origin();
        let leaves = self.inner.leaves.borrow();
        let mut cursor = 0usize;
        for leaf in &*leaves {
            let metrics = leaf.metrics.get();
            let end = cursor + metrics.bytes;
            if offset < end {
                let text = leaf.read();
                leaf.source.observe();
                return Some(RopeChunk {
                    text,
                    start,
                    metrics,
                });
            }
            start.byte += metrics.bytes;
            start.char_offset += metrics.chars;
            start.utf16_offset += metrics.utf16;
            start.line += metrics.line_breaks;
            if metrics.line_breaks == 0 {
                start.column += leaf.tail_chars.get();
            } else {
                start.column = leaf.tail_chars.get();
            }
            cursor = end;
        }
        None
    }

    pub fn cursor(&self, offset: usize) -> RopeCursor {
        RopeCursor {
            rope: self.clone(),
            next_offset: offset.min(self.len_bytes()),
        }
    }

    pub fn edit(&self, edit: RopeEdit) -> EditDelta {
        let delta = self.apply_edit(edit);
        self.inner.edit_events.emit(delta.clone());
        delta
    }

    pub fn edit_batch(&self, edits: impl IntoIterator<Item = RopeEdit>) -> Vec<EditDelta> {
        let mut deltas = Vec::new();
        for edit in edits {
            let delta = self.apply_edit(edit);
            self.inner.edit_events.emit(delta.clone());
            deltas.push(delta);
        }
        deltas
    }

    fn apply_edit(&self, edit: RopeEdit) -> EditDelta {
        assert!(edit.range.start <= edit.range.end, "invalid edit range");
        assert!(
            edit.range.end <= self.len_bytes(),
            "edit range out of bounds"
        );
        self.assert_char_boundary(edit.range.start);
        self.assert_char_boundary(edit.range.end);
        assert!(
            edit.insert.is_char_boundary(edit.insert.len()),
            "replacement text must be valid UTF-8"
        );

        let old_start = self.point_at(edit.range.start);
        let old_end = self.point_at(edit.range.end);
        let removed_text = self.slice(edit.range);
        let removed = TextMetrics::from_text(&removed_text);
        let inserted = TextMetrics::from_text(&edit.insert);

        let mut leaves = self.inner.leaves.borrow_mut();
        let mut cursor = 0usize;
        let mut first_index = None;
        let mut last_index = None;
        let mut prefix = String::new();
        let mut suffix = String::new();

        for (index, leaf) in leaves.iter().enumerate() {
            let text = leaf.read();
            let end = cursor + text.len();
            if first_index.is_none() && edit.range.start <= end {
                first_index = Some(index);
                let split = edit.range.start.saturating_sub(cursor);
                prefix.push_str(&text[..split]);
            }
            if first_index.is_some() && edit.range.end <= end {
                last_index = Some(index);
                let split = edit.range.end.saturating_sub(cursor);
                suffix.push_str(&text[split..]);
                break;
            }
            cursor = end;
        }

        let first_index = first_index.unwrap_or_else(|| leaves.len().saturating_sub(1));
        let last_index = last_index.unwrap_or(first_index);

        let mut replacement = prefix;
        replacement.push_str(&edit.insert);
        replacement.push_str(&suffix);

        let structure_changed =
            last_index > first_index || replacement.len() > DEFAULT_LEAF_MAX_BYTES;
        if !structure_changed {
            if let Some(leaf) = leaves.get(first_index) {
                leaf.set_text(replacement);
                leaf.source.trigger();
            }
        } else {
            let mut replacement_leaves = Vec::new();
            Self::append_chunk_into(
                &self.inner.reactor,
                self.inner.leaf_target_bytes,
                &mut replacement_leaves,
                &replacement,
            );
            if replacement_leaves.is_empty() {
                replacement_leaves.push(Rc::new(Leaf::new(&self.inner.reactor, String::new())));
            }

            let removed_span = leaves.splice(first_index..=last_index, replacement_leaves);
            let mut touched = BTreeSet::new();
            for leaf in removed_span {
                touched.insert(leaf.source.id());
            }
            for leaf in leaves.iter().skip(first_index) {
                touched.insert(leaf.source.id());
            }
            for leaf in &*leaves {
                if touched.contains(&leaf.source.id()) {
                    leaf.source.trigger();
                }
            }
            self.inner.structure.trigger();
        }

        self.inner
            .revision
            .set(self.inner.revision.get().wrapping_add(1));

        let new_end = old_start.advance(&edit.insert);
        drop(leaves);

        EditDelta {
            range: edit.range,
            removed_text,
            inserted_text: edit.insert,
            removed,
            inserted,
            start: old_start,
            old_end,
            new_end,
        }
    }

    fn total_metrics(&self) -> TextMetrics {
        self.inner
            .leaves
            .borrow()
            .iter()
            .fold(TextMetrics::default(), |sum, leaf| sum + leaf.metrics.get())
    }

    fn append_chunk_into(
        reactor: &Reactor,
        leaf_target_bytes: usize,
        out: &mut Vec<Rc<Leaf>>,
        text: &str,
    ) {
        if text.is_empty() {
            return;
        }

        let max_bytes = (leaf_target_bytes.max(32)) * 2;
        let mut start = 0usize;
        while start < text.len() {
            let mut end = min(text.len(), start + max_bytes);
            while end > start && !text.is_char_boundary(end) {
                end -= 1;
            }
            if end == start {
                end = text.len();
            }
            out.push(Rc::new(Leaf::new(reactor, text[start..end].to_string())));
            start = end;
        }
    }

    fn assert_char_boundary(&self, offset: usize) {
        if offset == self.len_bytes() {
            return;
        }
        let leaves = self.inner.leaves.borrow();
        let mut cursor = 0usize;
        for leaf in &*leaves {
            let bytes = leaf.metrics.get().bytes;
            let end = cursor + bytes;
            if offset < end {
                let text = leaf.read();
                assert!(
                    text.is_char_boundary(offset - cursor),
                    "offset must be a char boundary"
                );
                return;
            }
            cursor = end;
        }
    }
}

impl fmt::Display for ReactiveRope {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.slice(TextRange::new(0, self.len_bytes())))
    }
}

impl RopeChunk {
    pub fn text(&self) -> &str {
        &self.text
    }
}

impl RopeCursor {
    pub fn offset(&self) -> usize {
        self.next_offset
    }

    pub fn peek_chunk(&self) -> Option<RopeChunk> {
        let chunk = self.rope.chunk_at(self.next_offset)?;
        let local = self.next_offset.saturating_sub(chunk.start.byte);
        if local == 0 {
            return Some(chunk);
        }

        let partial = &chunk.text()[local..];
        Some(RopeChunk {
            text: Rc::from(partial),
            start: self.rope.point_at(self.next_offset),
            metrics: TextMetrics::from_text(partial),
        })
    }

    pub fn next_chunk(&mut self) -> Option<RopeChunk> {
        let chunk = self.peek_chunk()?;
        let end = chunk.start.byte + chunk.metrics.bytes;
        self.next_offset = end;
        Some(chunk)
    }

    pub fn read_bytes(&mut self, len: usize) -> String {
        let start = self.next_offset;
        let end = min(self.rope.len_bytes(), start + len);
        let slice = self.rope.slice(TextRange::new(start, end));
        self.next_offset = end;
        slice
    }
}

impl Leaf {
    fn new(reactor: &Reactor, text: String) -> Self {
        let text: Rc<str> = Rc::from(text);
        let metrics = TextMetrics::from_text(&text);
        let tail_chars = tail_chars(&text);
        Self {
            source: source_in(reactor),
            text: RefCell::new(text),
            metrics: Cell::new(metrics),
            tail_chars: Cell::new(tail_chars),
        }
    }

    fn read(&self) -> Rc<str> {
        self.source.observe();
        self.text.borrow().clone()
    }

    fn set_text(&self, text: String) {
        let text: Rc<str> = Rc::from(text);
        self.metrics.set(TextMetrics::from_text(&text));
        self.tail_chars.set(tail_chars(&text));
        *self.text.borrow_mut() = text;
    }
}

fn tail_chars(text: &str) -> usize {
    match text.rfind('\n') {
        Some(index) => text[index + 1..].chars().count(),
        None => text.chars().count(),
    }
}

#[cfg(test)]
mod tests {
    use std::cell::Cell as Counter;
    use std::cell::RefCell;
    use std::panic::{AssertUnwindSafe, catch_unwind};
    use std::rc::Rc;

    use ruin_reactivity::{Reactor, thunk_in};

    use crate::{Anchor, AnchorBias, ReactiveRope, RopeBuilder, RopeEdit, TextRange};

    #[test]
    fn edits_preserve_text_and_metrics() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "hello\nworld");

        let delta = rope.edit(RopeEdit::replace(TextRange::new(5, 6), ", "));

        assert_eq!(rope.to_string(), "hello, world");
        assert_eq!(delta.removed_text, "\n");
        assert_eq!(delta.inserted_text, ", ");
        assert_eq!(rope.snapshot_metrics().text.bytes, "hello, world".len());
        assert_eq!(rope.snapshot_metrics().text.line_breaks, 0);
    }

    #[test]
    fn cursor_walks_tokens_across_leaf_boundaries() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(4)
            .push("alpha")
            .push("beta")
            .build();
        let mut cursor = rope.cursor(3);

        assert_eq!(cursor.read_bytes(4), "habe");
        let rest = cursor.next_chunk().expect("remaining chunk");
        assert_eq!(rest.text(), "ta");
    }

    #[test]
    fn anchors_map_through_insertions() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "abcdef");
        let mut left = Anchor::new(3, AnchorBias::Left);
        let mut right = Anchor::new(3, AnchorBias::Right);

        let delta = rope.edit(RopeEdit::insert(3, "XYZ"));
        left.map_through(&delta);
        right.map_through(&delta);

        assert_eq!(left.byte, 3);
        assert_eq!(right.byte, 6);
    }

    #[test]
    fn disjoint_leaf_reads_do_not_recompute_on_local_edit() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(64)
            .push("a".repeat(80))
            .push("b".repeat(80))
            .build();

        let first_runs = Rc::new(Counter::new(0usize));
        let second_runs = Rc::new(Counter::new(0usize));

        let first = thunk_in(&reactor, {
            let rope = rope.clone();
            let first_runs = Rc::clone(&first_runs);
            move || {
                first_runs.set(first_runs.get() + 1);
                rope.slice(TextRange::new(0, 10))
            }
        });

        let second = thunk_in(&reactor, {
            let rope = rope.clone();
            let second_runs = Rc::clone(&second_runs);
            move || {
                second_runs.set(second_runs.get() + 1);
                rope.slice(TextRange::new(90, 100))
            }
        });

        assert_eq!(first.get(), "aaaaaaaaaa");
        assert_eq!(second.get(), "bbbbbbbbbb");
        rope.edit(RopeEdit::replace(TextRange::new(2, 4), "zz"));

        assert_eq!(first.get(), "aazzaaaaaa");
        assert_eq!(second.get(), "bbbbbbbbbb");
        assert_eq!(first_runs.get(), 2);
        assert_eq!(second_runs.get(), 1);
    }

    #[test]
    fn point_at_tracks_multibyte_offsets() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "a\né🙂");

        assert_eq!(rope.point_at(0).byte, 0);
        assert_eq!(rope.point_at(2).line, 1);
        assert_eq!(rope.point_at(4).char_offset, 3);
        assert_eq!(rope.point_at("a\né🙂".len()).utf16_offset, 5);
        assert_eq!(rope.point_at("a\né".len()).column, 1);
    }

    #[test]
    fn point_at_tracks_column_across_leaves() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(4)
            .push("ab\n")
            .push("cde")
            .build();

        let point = rope.point_at("ab\ncd".len());
        assert_eq!(point.line, 1);
        assert_eq!(point.column, 2);
    }

    #[test]
    fn chunk_at_and_peek_chunk_respect_boundaries() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(4)
            .push("abcd")
            .push("efgh")
            .build();

        let first = rope.chunk_at(0).expect("chunk at start");
        assert_eq!(first.text(), "abcd");

        let second = rope.chunk_at(4).expect("chunk at split");
        assert_eq!(second.text(), "efgh");
        assert!(rope.chunk_at(8).is_none());

        let mut cursor = rope.cursor(2);
        let partial = cursor.peek_chunk().expect("partial chunk");
        assert_eq!(partial.text(), "cd");
        let advanced = cursor.next_chunk().expect("advanced chunk");
        assert_eq!(advanced.text(), "cd");
        assert_eq!(cursor.offset(), 4);
    }

    #[test]
    fn cursor_stops_at_end_of_document() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(4)
            .push("abcd")
            .push("ef")
            .build();
        let mut cursor = rope.cursor(0);
        let mut chunks = Vec::new();
        while let Some(chunk) = cursor.next_chunk() {
            chunks.push(chunk.text().to_string());
        }

        assert_eq!(chunks, vec!["abcd".to_string(), "ef".to_string()]);
        assert!(cursor.next_chunk().is_none());
    }

    #[test]
    fn batch_edits_return_deltas_and_increment_revision() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "abcdef");
        let before = rope.snapshot_metrics().revision;

        let deltas = rope.edit_batch([
            RopeEdit::replace(TextRange::new(1, 3), "ZZ"),
            RopeEdit::delete(TextRange::new(4, 6)),
        ]);

        assert_eq!(deltas.len(), 2);
        assert_eq!(rope.to_string(), "aZZd");
        assert_eq!(rope.snapshot_metrics().revision, before + 2);
    }

    #[test]
    fn delete_to_empty_preserves_valid_empty_rope() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "hello");

        let delta = rope.edit(RopeEdit::delete(TextRange::new(0, 5)));

        assert_eq!(delta.removed_text, "hello");
        assert!(rope.is_empty());
        assert_eq!(rope.to_string(), "");
        assert_eq!(rope.snapshot_metrics().leaves, 1);
    }

    #[test]
    fn structure_changing_edit_updates_leaf_count() {
        let reactor = Reactor::new();
        let rope = RopeBuilder::new(&reactor)
            .leaf_target_bytes(8)
            .push("abcdefgh")
            .push("ijklmnop")
            .build();
        let before = rope.snapshot_metrics();

        rope.edit(RopeEdit::insert(8, "QRSTUVWXYZ0123456789"));
        let after = rope.snapshot_metrics();

        assert!(after.leaves >= before.leaves);
        assert!(after.text.bytes > before.text.bytes);
    }

    #[test]
    fn invalid_char_boundary_panics() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "é");

        let panic = catch_unwind(AssertUnwindSafe(|| {
            let _ = rope.slice(TextRange::new(1, 2));
        }));

        assert!(panic.is_err());
    }

    #[test]
    fn edit_events_emit_immediately() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "abc");
        let seen = Rc::new(RefCell::new(Vec::new()));
        let _sub = rope.edit_events().subscribe({
            let seen = Rc::clone(&seen);
            move |delta| {
                seen.borrow_mut()
                    .push((delta.range, delta.inserted_text.clone()))
            }
        });

        rope.edit(RopeEdit::insert(1, "Z"));

        assert_eq!(&*seen.borrow(), &[(TextRange::new(1, 1), "Z".to_string())]);
    }
}
