use core::fmt;
use std::cell::RefCell;
use std::rc::Rc;

use ruin_reactivity::{Cell, Event, Subscription, cell_in, event_in};

use crate::{Anchor, AnchorBias, EditDelta, ReactiveRope, TextPoint, TextRange};

const SIGILS: &[&str] = &[
    "<<=", ">>=", "..=", "::", "->", "=>", "==", "!=", "<=", ">=", "&&", "||", "+=", "-=", "*=",
    "/=", "%=", "&=", "|=", "^=", "<<", ">>", "..", "+", "-", "*", "/", "%", "&", "|", "^", "!",
    "~", "=", "<", ">", ":", ";", "?", ".", ",", "@", "#", "$",
];

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DelimiterKind {
    Parenthesis,
    Bracket,
    Brace,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StringDelimiter {
    SingleQuote,
    DoubleQuote,
    Backtick,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StringFragment {
    Text {
        range: TextRange,
        text: String,
    },
    Interpolation {
        range: TextRange,
        child_index: usize,
    },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NumberBase {
    Binary,
    Octal,
    Decimal,
    Hexadecimal,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NumberSuffix {
    F32,
    F64,
    BigFloat,
    Decimal128,
    BigDecimal,
    U8,
    U16,
    U32,
    U64,
    U128,
    I8,
    I16,
    I32,
    I64,
    I128,
    BigUint,
    BigInt,
}

impl NumberSuffix {
    fn is_integer(self) -> bool {
        !matches!(
            self,
            Self::F32 | Self::F64 | Self::BigFloat | Self::Decimal128 | Self::BigDecimal
        )
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NumberLiteral {
    pub raw: String,
    pub base: NumberBase,
    pub has_radix_point: bool,
    pub suffix: Option<NumberSuffix>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TokenTreeNodeKind {
    Body,
    List {
        delimiter: DelimiterKind,
        terminated: bool,
    },
    Symbol {
        text: String,
    },
    Sigil {
        text: String,
    },
    String {
        delimiter: StringDelimiter,
        terminated: bool,
        fragments: Vec<StringFragment>,
    },
    Number(NumberLiteral),
    Error {
        message: String,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenTreeNode {
    pub id: u64,
    pub local_range: TextRange,
    pub kind: TokenTreeNodeKind,
    pub children: Vec<TokenTreeNode>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TriviaKind {
    LineComment,
    BlockComment,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TriviaComment {
    pub kind: TriviaKind,
    pub range: TextRange,
    pub text: String,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TriviaStore {
    comments: Vec<TriviaComment>,
}

impl TriviaStore {
    pub fn comments(&self) -> &[TriviaComment] {
        &self.comments
    }

    pub fn comments_in_range(&self, range: TextRange) -> Vec<&TriviaComment> {
        self.comments
            .iter()
            .filter(|comment| comment.range.start < range.end && comment.range.end > range.start)
            .collect()
    }

    pub fn comments_before(&self, offset: usize) -> Vec<&TriviaComment> {
        self.comments
            .iter()
            .filter(|comment| comment.range.end <= offset)
            .collect()
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TokenTreeDiagnosticKind {
    UnterminatedList,
    UnmatchedClosingDelimiter,
    UnterminatedString,
    UnterminatedInterpolation,
    InvalidEscape,
    InvalidUnicodeEscape,
    UnterminatedBlockComment,
    InvalidNumber,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenTreeDiagnostic {
    pub kind: TokenTreeDiagnosticKind,
    pub range: TextRange,
    pub message: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenTreeParse {
    text: String,
    pub root_anchor: Anchor,
    pub root: TokenTreeNode,
    pub diagnostics: Vec<TokenTreeDiagnostic>,
    pub trivia: TriviaStore,
    pub stats: TokenTreeStats,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenTreeStats {
    pub reused_nodes: usize,
    pub total_nodes: usize,
    pub reparsed_range: TextRange,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TokenTreeUpdate {
    pub delta: EditDelta,
    pub stats: TokenTreeStats,
    pub diagnostics_changed: bool,
    pub trivia_changed: bool,
}

pub struct ReactiveTokenTree {
    rope: ReactiveRope,
    state: Cell<TokenTreeParse>,
    last_update: Cell<Option<TokenTreeUpdate>>,
    updates: Event<TokenTreeUpdate>,
    _subscription: Subscription,
}

impl TokenTreeParse {
    pub fn root(&self) -> &TokenTreeNode {
        &self.root
    }

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

    pub fn diagnostics(&self) -> &[TokenTreeDiagnostic] {
        &self.diagnostics
    }

    pub fn trivia(&self) -> &TriviaStore {
        &self.trivia
    }

    pub fn stats(&self) -> &TokenTreeStats {
        &self.stats
    }

    pub fn absolute_range(&self, node_id: u64) -> Option<TextRange> {
        fn walk(node: &TokenTreeNode, parent_start: usize, wanted: u64) -> Option<TextRange> {
            let range = absolute_range(node, parent_start);
            if node.id == wanted {
                return Some(range);
            }
            for child in &node.children {
                if let Some(found) = walk(child, range.start, wanted) {
                    return Some(found);
                }
            }
            None
        }

        walk(&self.root, self.root_anchor.byte, node_id)
    }

    pub fn absolute_span(
        &self,
        rope: &ReactiveRope,
        node_id: u64,
    ) -> Option<(TextPoint, TextPoint)> {
        let range = self.absolute_range(node_id)?;
        Some((rope.point_at(range.start), rope.point_at(range.end)))
    }
}

impl ReactiveTokenTree {
    pub fn new(rope: &ReactiveRope) -> Self {
        let reactor = rope.reactor();
        let mut parser = TokenTreeParser::new();
        let initial = parser.parse(rope);
        let state = cell_in(&reactor, initial);
        let last_update = cell_in(&reactor, None::<TokenTreeUpdate>);
        let updates = event_in(&reactor);
        let parser = Rc::new(RefCell::new(parser));
        let rope_clone = rope.clone();
        let state_clone = state.clone();
        let last_update_clone = last_update.clone();
        let updates_clone = updates.clone();
        let subscription = rope.edit_events().subscribe({
            let parser = Rc::clone(&parser);
            move |delta| {
                let previous = state_clone.get();
                let next = parser.borrow_mut().reparse(&rope_clone, &previous, delta);
                let update = TokenTreeUpdate {
                    delta: delta.clone(),
                    stats: next.stats.clone(),
                    diagnostics_changed: previous.diagnostics != next.diagnostics,
                    trivia_changed: previous.trivia != next.trivia,
                };
                last_update_clone.replace(Some(update.clone()));
                state_clone.replace(next);
                updates_clone.emit(update);
            }
        });

        Self {
            rope: rope.clone(),
            state,
            last_update,
            updates,
            _subscription: subscription,
        }
    }

    pub fn rope(&self) -> ReactiveRope {
        self.rope.clone()
    }

    pub fn snapshot(&self) -> TokenTreeParse {
        self.state.get()
    }

    pub fn with_snapshot<R>(&self, f: impl FnOnce(&TokenTreeParse) -> R) -> R {
        self.state.with(f)
    }

    pub fn last_update(&self) -> Option<TokenTreeUpdate> {
        self.last_update.get()
    }

    pub fn with_last_update<R>(&self, f: impl FnOnce(&Option<TokenTreeUpdate>) -> R) -> R {
        self.last_update.with(f)
    }

    pub fn updates(&self) -> Event<TokenTreeUpdate> {
        self.updates.clone()
    }
}

#[derive(Default)]
pub struct TokenTreeParser {
    next_id: u64,
}

struct ReuseContext<'a, 'count> {
    old_text: &'a str,
    new_text: &'a str,
    delta: &'a EditDelta,
    reused_nodes: &'count mut usize,
}

impl TokenTreeParser {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn parse(&mut self, rope: &ReactiveRope) -> TokenTreeParse {
        let text = read_rope_text(rope);
        let raw = parse_raw(rope);
        let root = self.materialize_fresh(&raw.root, 0);
        let total_nodes = count_nodes(&root);
        TokenTreeParse {
            text,
            root_anchor: Anchor::new(0, AnchorBias::Left),
            root,
            diagnostics: raw.diagnostics,
            trivia: raw.trivia,
            stats: TokenTreeStats {
                reused_nodes: 0,
                total_nodes,
                reparsed_range: TextRange::new(0, rope.len_bytes()),
            },
        }
    }

    pub fn reparse(
        &mut self,
        rope: &ReactiveRope,
        previous: &TokenTreeParse,
        delta: &EditDelta,
    ) -> TokenTreeParse {
        let text = read_rope_text(rope);
        let raw = parse_raw(rope);
        let mut root_anchor = previous.root_anchor;
        root_anchor.map_through(delta);
        let mut reused_nodes = 0usize;
        let mut reuse = ReuseContext {
            old_text: &previous.text,
            new_text: &text,
            delta,
            reused_nodes: &mut reused_nodes,
        };
        let root = self.reuse_node(
            &raw.root,
            Some(&previous.root),
            previous.root_anchor.byte,
            root_anchor.byte,
            &mut reuse,
        );
        let total_nodes = count_nodes(&root);

        TokenTreeParse {
            text,
            root_anchor,
            root,
            diagnostics: raw.diagnostics,
            trivia: raw.trivia,
            stats: TokenTreeStats {
                reused_nodes,
                total_nodes,
                reparsed_range: TextRange::new(0, rope.len_bytes()),
            },
        }
    }

    fn materialize_fresh(&mut self, parsed: &RawNode, parent_start: usize) -> TokenTreeNode {
        TokenTreeNode {
            id: self.alloc_id(),
            local_range: localize(parsed.range, parent_start),
            kind: parsed.kind.clone(),
            children: parsed
                .children
                .iter()
                .map(|child| self.materialize_fresh(child, parsed.range.start))
                .collect(),
        }
    }

    fn reuse_node(
        &mut self,
        parsed: &RawNode,
        old: Option<&TokenTreeNode>,
        old_parent_start: usize,
        new_parent_start: usize,
        reuse: &mut ReuseContext<'_, '_>,
    ) -> TokenTreeNode {
        let parsed_local = localize(parsed.range, new_parent_start);
        if let Some(old) = old.filter(|old| {
            can_reuse_node(
                old,
                old_parent_start,
                parsed,
                reuse.old_text,
                reuse.new_text,
                reuse.delta,
            )
        }) {
            *reuse.reused_nodes += count_nodes(old);
            return TokenTreeNode {
                id: old.id,
                local_range: parsed_local,
                kind: old.kind.clone(),
                children: old.children.clone(),
            };
        }

        let children = if let Some(old) = old {
            let mut used = vec![false; old.children.len()];
            parsed
                .children
                .iter()
                .enumerate()
                .map(|(index, child)| {
                    let candidate = old
                        .children
                        .get(index)
                        .filter(|candidate| !used[index] && can_descend_into_node(candidate, child))
                        .inspect(|_| {
                            used[index] = true;
                        })
                        .or_else(|| {
                            old.children.iter().enumerate().find_map(
                                |(candidate_index, candidate)| {
                                    if used[candidate_index]
                                        || !can_descend_into_node(candidate, child)
                                    {
                                        return None;
                                    }
                                    used[candidate_index] = true;
                                    Some(candidate)
                                },
                            )
                        });

                    self.reuse_node(
                        child,
                        candidate,
                        absolute_range(old, old_parent_start).start,
                        parsed.range.start,
                        reuse,
                    )
                })
                .collect()
        } else {
            parsed
                .children
                .iter()
                .map(|child| {
                    self.reuse_node(child, None, old_parent_start, parsed.range.start, reuse)
                })
                .collect()
        };

        TokenTreeNode {
            id: self.alloc_id(),
            local_range: parsed_local,
            kind: parsed.kind.clone(),
            children,
        }
    }

    fn alloc_id(&mut self) -> u64 {
        self.next_id = self.next_id.wrapping_add(1);
        self.next_id
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct RawNode {
    range: TextRange,
    kind: TokenTreeNodeKind,
    children: Vec<RawNode>,
}

struct RawParse {
    root: RawNode,
    diagnostics: Vec<TokenTreeDiagnostic>,
    trivia: TriviaStore,
}

struct Cursor<'a> {
    rope: &'a ReactiveRope,
    offset: usize,
    diagnostics: Vec<TokenTreeDiagnostic>,
    trivia: TriviaStore,
}

fn parse_raw(rope: &ReactiveRope) -> RawParse {
    let mut cursor = Cursor {
        rope,
        offset: 0,
        diagnostics: Vec::new(),
        trivia: TriviaStore::default(),
    };
    let children = cursor.parse_sequence(None);
    let root = RawNode {
        range: TextRange::new(0, rope.len_bytes()),
        kind: TokenTreeNodeKind::Body,
        children,
    };
    RawParse {
        root,
        diagnostics: cursor.diagnostics,
        trivia: cursor.trivia,
    }
}

impl<'a> Cursor<'a> {
    fn parse_sequence(&mut self, terminator: Option<char>) -> Vec<RawNode> {
        let mut nodes = Vec::new();
        let mut terminated = terminator.is_none();
        loop {
            self.skip_ws_and_comments();
            if self.is_eof() {
                break;
            }

            if let Some(close) = terminator
                && self.peek_char() == Some(close)
            {
                self.bump_char();
                terminated = true;
                break;
            }

            let Some(ch) = self.peek_char() else {
                break;
            };
            if let Some(delimiter) = opening_delimiter(ch) {
                nodes.push(self.parse_list(delimiter));
                continue;
            }
            if matches!(ch, ')' | ']' | '}') {
                let start = self.offset;
                self.bump_char();
                self.diagnostics.push(TokenTreeDiagnostic {
                    kind: TokenTreeDiagnosticKind::UnmatchedClosingDelimiter,
                    range: TextRange::new(start, self.offset),
                    message: "unmatched closing delimiter".into(),
                });
                nodes.push(RawNode {
                    range: TextRange::new(start, self.offset),
                    kind: TokenTreeNodeKind::Error {
                        message: "unmatched closing delimiter".into(),
                    },
                    children: Vec::new(),
                });
                continue;
            }
            if matches!(ch, '\'' | '"' | '`') {
                nodes.push(self.parse_string());
                continue;
            }
            if ch.is_ascii_digit() {
                nodes.push(self.parse_number());
                continue;
            }
            if let Some(sigil) = self.longest_sigil() {
                nodes.push(self.parse_sigil(sigil));
                continue;
            }
            nodes.push(self.parse_symbol_or_error());
        }

        if let Some(close) = terminator
            && !terminated
        {
            self.diagnostics.push(TokenTreeDiagnostic {
                kind: TokenTreeDiagnosticKind::UnterminatedList,
                range: TextRange::new(self.offset, self.offset),
                message: format!("unterminated list; expected `{close}`"),
            });
        }
        nodes
    }

    fn parse_list(&mut self, delimiter: DelimiterKind) -> RawNode {
        let start = self.offset;
        let (_, close) = delimiter_chars(delimiter);
        self.bump_char();
        let children = self.parse_sequence(Some(close));
        let terminated = self.slice(self.offset.saturating_sub(close.len_utf8()), self.offset)
            == close.to_string();
        RawNode {
            range: TextRange::new(start, self.offset),
            kind: TokenTreeNodeKind::List {
                delimiter,
                terminated,
            },
            children,
        }
    }

    fn parse_sigil(&mut self, sigil: &str) -> RawNode {
        let start = self.offset;
        self.offset += sigil.len();
        RawNode {
            range: TextRange::new(start, self.offset),
            kind: TokenTreeNodeKind::Sigil {
                text: sigil.to_string(),
            },
            children: Vec::new(),
        }
    }

    fn parse_symbol_or_error(&mut self) -> RawNode {
        let start = self.offset;
        let Some(first) = self.peek_char() else {
            return RawNode {
                range: TextRange::new(start, start),
                kind: TokenTreeNodeKind::Error {
                    message: "unexpected end of input".into(),
                },
                children: Vec::new(),
            };
        };

        if !self.is_symbol_start(first) {
            self.bump_char();
            return RawNode {
                range: TextRange::new(start, self.offset),
                kind: TokenTreeNodeKind::Error {
                    message: format!("unexpected character `{first}`"),
                },
                children: Vec::new(),
            };
        }

        self.bump_char();
        while let Some(ch) = self.peek_char() {
            if !self.is_symbol_continue(ch) {
                break;
            }
            self.bump_char();
        }
        RawNode {
            range: TextRange::new(start, self.offset),
            kind: TokenTreeNodeKind::Symbol {
                text: self.slice(start, self.offset),
            },
            children: Vec::new(),
        }
    }

    fn parse_string(&mut self) -> RawNode {
        let start = self.offset;
        let delimiter_char = self
            .bump_char()
            .expect("string should start with delimiter");
        let delimiter = match delimiter_char {
            '\'' => StringDelimiter::SingleQuote,
            '"' => StringDelimiter::DoubleQuote,
            '`' => StringDelimiter::Backtick,
            _ => unreachable!(),
        };

        let mut fragments = Vec::new();
        let mut children = Vec::new();
        let mut text_start = self.offset;
        let mut terminated = false;
        while let Some(ch) = self.peek_char() {
            if ch == delimiter_char {
                if text_start < self.offset {
                    fragments.push(StringFragment::Text {
                        range: TextRange::new(text_start, self.offset),
                        text: self.slice(text_start, self.offset),
                    });
                }
                self.bump_char();
                terminated = true;
                break;
            }

            if delimiter == StringDelimiter::Backtick && self.starts_with("$(") {
                if text_start < self.offset {
                    fragments.push(StringFragment::Text {
                        range: TextRange::new(text_start, self.offset),
                        text: self.slice(text_start, self.offset),
                    });
                }
                let interp_start = self.offset;
                self.offset += 2;
                let interpolation_children = self.parse_sequence(Some(')'));
                let terminated_interp =
                    self.slice(self.offset.saturating_sub(1), self.offset) == ")";
                if !terminated_interp {
                    self.diagnostics.push(TokenTreeDiagnostic {
                        kind: TokenTreeDiagnosticKind::UnterminatedInterpolation,
                        range: TextRange::new(interp_start, self.offset),
                        message: "unterminated string interpolation".into(),
                    });
                }
                let child_index = children.len();
                children.push(RawNode {
                    range: TextRange::new(interp_start + 1, self.offset),
                    kind: TokenTreeNodeKind::List {
                        delimiter: DelimiterKind::Parenthesis,
                        terminated: terminated_interp,
                    },
                    children: interpolation_children,
                });
                fragments.push(StringFragment::Interpolation {
                    range: TextRange::new(interp_start, self.offset),
                    child_index,
                });
                text_start = self.offset;
                continue;
            }

            if ch == '\\' {
                if let Err((range, kind, message)) = self.consume_escape(delimiter) {
                    self.diagnostics.push(TokenTreeDiagnostic {
                        kind,
                        range,
                        message: message.clone(),
                    });
                }
                continue;
            }

            self.bump_char();
        }

        if !terminated {
            if text_start < self.offset {
                fragments.push(StringFragment::Text {
                    range: TextRange::new(text_start, self.offset),
                    text: self.slice(text_start, self.offset),
                });
            }
            self.diagnostics.push(TokenTreeDiagnostic {
                kind: TokenTreeDiagnosticKind::UnterminatedString,
                range: TextRange::new(start, self.offset),
                message: "unterminated string".into(),
            });
        }

        RawNode {
            range: TextRange::new(start, self.offset),
            kind: TokenTreeNodeKind::String {
                delimiter,
                terminated,
                fragments,
            },
            children,
        }
    }

    fn parse_number(&mut self) -> RawNode {
        let start = self.offset;
        let (literal, end, maybe_error) = self.scan_number(start);
        self.offset = end;
        if let Some((kind, message)) = maybe_error {
            self.diagnostics.push(TokenTreeDiagnostic {
                kind,
                range: TextRange::new(start, end),
                message: message.clone(),
            });
            return RawNode {
                range: TextRange::new(start, end),
                kind: TokenTreeNodeKind::Error { message },
                children: Vec::new(),
            };
        }
        RawNode {
            range: TextRange::new(start, end),
            kind: TokenTreeNodeKind::Number(literal),
            children: Vec::new(),
        }
    }

    fn skip_ws_and_comments(&mut self) {
        loop {
            while let Some(ch) = self.peek_char() {
                if ch.is_whitespace() {
                    self.bump_char();
                } else {
                    break;
                }
            }

            if self.starts_with("//") {
                let start = self.offset;
                self.offset += 2;
                while let Some(ch) = self.peek_char() {
                    if ch == '\n' {
                        break;
                    }
                    self.bump_char();
                }
                self.trivia.comments.push(TriviaComment {
                    kind: TriviaKind::LineComment,
                    range: TextRange::new(start, self.offset),
                    text: self.slice(start, self.offset),
                });
                continue;
            }

            if self.starts_with("/*") {
                let start = self.offset;
                self.offset += 2;
                let mut depth = 1usize;
                while !self.is_eof() {
                    if self.starts_with("/*") {
                        depth += 1;
                        self.offset += 2;
                    } else if self.starts_with("*/") {
                        depth -= 1;
                        self.offset += 2;
                        if depth == 0 {
                            break;
                        }
                    } else {
                        self.bump_char();
                    }
                }

                if depth != 0 {
                    self.diagnostics.push(TokenTreeDiagnostic {
                        kind: TokenTreeDiagnosticKind::UnterminatedBlockComment,
                        range: TextRange::new(start, self.offset),
                        message: "unterminated block comment".into(),
                    });
                }

                self.trivia.comments.push(TriviaComment {
                    kind: TriviaKind::BlockComment,
                    range: TextRange::new(start, self.offset),
                    text: self.slice(start, self.offset),
                });
                continue;
            }

            break;
        }
    }

    fn consume_escape(
        &mut self,
        delimiter: StringDelimiter,
    ) -> Result<(), (TextRange, TokenTreeDiagnosticKind, String)> {
        let start = self.offset;
        self.bump_char();
        match self.bump_char() {
            Some('n' | 'r' | 't' | '\\' | '0') => Ok(()),
            Some('\'') if delimiter != StringDelimiter::DoubleQuote => Ok(()),
            Some('"') if delimiter != StringDelimiter::SingleQuote => Ok(()),
            Some('`') if delimiter == StringDelimiter::Backtick => Ok(()),
            Some('u') => {
                if self.bump_char() != Some('{') {
                    return Err((
                        TextRange::new(start, self.offset),
                        TokenTreeDiagnosticKind::InvalidUnicodeEscape,
                        "unicode escapes must be of the form \\u{...}".into(),
                    ));
                }
                let digits_start = self.offset;
                while let Some(ch) = self.peek_char() {
                    if ch == '}' {
                        break;
                    }
                    if !ch.is_ascii_hexdigit() {
                        return Err((
                            TextRange::new(start, self.offset + ch.len_utf8()),
                            TokenTreeDiagnosticKind::InvalidUnicodeEscape,
                            "unicode escape contains non-hex digit".into(),
                        ));
                    }
                    self.bump_char();
                }
                if self.peek_char() != Some('}') || self.offset == digits_start {
                    return Err((
                        TextRange::new(start, self.offset),
                        TokenTreeDiagnosticKind::InvalidUnicodeEscape,
                        "unicode escape must contain at least one hex digit and terminate with `}`"
                            .into(),
                    ));
                }
                self.bump_char();
                Ok(())
            }
            Some('x') => Err((
                TextRange::new(start, self.offset),
                TokenTreeDiagnosticKind::InvalidEscape,
                "hex escapes are not supported".into(),
            )),
            Some(other) => Err((
                TextRange::new(start, self.offset),
                TokenTreeDiagnosticKind::InvalidEscape,
                format!("invalid escape `\\{other}`"),
            )),
            None => Err((
                TextRange::new(start, self.offset),
                TokenTreeDiagnosticKind::UnterminatedString,
                "unterminated escape".into(),
            )),
        }
    }

    fn is_eof(&self) -> bool {
        self.offset >= self.rope.len_bytes()
    }

    fn peek_char(&self) -> Option<char> {
        self.rope
            .cursor(self.offset)
            .peek_chunk()
            .and_then(|chunk| chunk.text().chars().next())
    }

    fn bump_char(&mut self) -> Option<char> {
        let ch = self.peek_char()?;
        self.offset += ch.len_utf8();
        Some(ch)
    }

    fn starts_with(&self, pattern: &str) -> bool {
        self.starts_with_at(self.offset, pattern)
    }

    fn starts_with_at(&self, offset: usize, pattern: &str) -> bool {
        rope_starts_with_at(self.rope, offset, pattern)
    }

    fn slice(&self, start: usize, end: usize) -> String {
        self.rope.slice(TextRange::new(start, end))
    }

    fn longest_sigil(&self) -> Option<&'static str> {
        longest_sigil_at(self.rope, self.offset)
    }

    fn is_symbol_start(&self, ch: char) -> bool {
        !ch.is_ascii_digit() && self.is_symbol_continue(ch)
    }

    fn is_symbol_continue(&self, ch: char) -> bool {
        if ch.is_whitespace() || ch.is_control() {
            return false;
        }
        if matches!(ch, '(' | ')' | '[' | ']' | '{' | '}' | '\'' | '"' | '`') {
            return false;
        }
        if self.starts_with("//") || self.starts_with("/*") {
            return false;
        }
        self.longest_sigil().is_none()
    }

    fn scan_number(
        &self,
        start: usize,
    ) -> (
        NumberLiteral,
        usize,
        Option<(TokenTreeDiagnosticKind, String)>,
    ) {
        let mut cursor = start;
        let mut base = NumberBase::Decimal;
        if self.starts_with_at(start, "0b") {
            base = NumberBase::Binary;
            cursor += 2;
        } else if self.starts_with_at(start, "0o") {
            base = NumberBase::Octal;
            cursor += 2;
        } else if self.starts_with_at(start, "0d") {
            base = NumberBase::Decimal;
            cursor += 2;
        } else if self.starts_with_at(start, "0x") {
            base = NumberBase::Hexadecimal;
            cursor += 2;
        }

        let mut saw_digit = false;
        let mut saw_radix = false;
        while let Some(ch) = self.char_at(cursor) {
            if ch == '_' {
                cursor += 1;
                continue;
            }
            if ch == '.' && base == NumberBase::Decimal && !saw_radix {
                saw_radix = true;
                cursor += 1;
                continue;
            }
            if is_digit_for_base(ch, base) {
                saw_digit = true;
                cursor += ch.len_utf8();
                continue;
            }
            break;
        }

        let suffix_start = cursor;
        while let Some(ch) = self.char_at(cursor) {
            if ch.is_ascii_alphanumeric() {
                cursor += ch.len_utf8();
            } else {
                break;
            }
        }

        let raw = self.slice(start, cursor);
        let suffix_raw = self.slice(suffix_start, cursor);
        let suffix = parse_suffix(&suffix_raw);
        let literal = NumberLiteral {
            raw: raw.clone(),
            base,
            has_radix_point: saw_radix,
            suffix,
        };

        if !saw_digit {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    "number literal must contain digits".into(),
                )),
            );
        }
        if suffix_raw.contains('_') {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    "numeric suffix cannot contain underscores".into(),
                )),
            );
        }
        if suffix.is_none() && !suffix_raw.is_empty() {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    format!("unknown numeric suffix `{suffix_raw}`"),
                )),
            );
        }
        if saw_radix && suffix.is_some_and(NumberSuffix::is_integer) {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    "integer-suffixed number cannot contain a radix point".into(),
                )),
            );
        }
        if saw_radix && base != NumberBase::Decimal {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    "only decimal numbers may contain a radix point".into(),
                )),
            );
        }
        if !number_body_valid(&raw, base, saw_radix, suffix_start - start) {
            return (
                literal,
                cursor,
                Some((
                    TokenTreeDiagnosticKind::InvalidNumber,
                    "invalid digits for numeric base".into(),
                )),
            );
        }

        (literal, cursor, None)
    }

    fn char_at(&self, offset: usize) -> Option<char> {
        self.rope
            .cursor(offset)
            .peek_chunk()
            .and_then(|chunk| chunk.text().chars().next())
    }
}

fn delimiter_chars(kind: DelimiterKind) -> (char, char) {
    match kind {
        DelimiterKind::Parenthesis => ('(', ')'),
        DelimiterKind::Bracket => ('[', ']'),
        DelimiterKind::Brace => ('{', '}'),
    }
}

fn opening_delimiter(ch: char) -> Option<DelimiterKind> {
    match ch {
        '(' => Some(DelimiterKind::Parenthesis),
        '[' => Some(DelimiterKind::Bracket),
        '{' => Some(DelimiterKind::Brace),
        _ => None,
    }
}

fn longest_sigil_at(rope: &ReactiveRope, offset: usize) -> Option<&'static str> {
    SIGILS
        .iter()
        .copied()
        .find(|sigil| rope_starts_with_at(rope, offset, sigil))
}

fn rope_starts_with_at(rope: &ReactiveRope, offset: usize, pattern: &str) -> bool {
    let mut cursor = offset;
    for expected in pattern.chars() {
        let Some(found) = rope
            .cursor(cursor)
            .peek_chunk()
            .and_then(|chunk| chunk.text().chars().next())
        else {
            return false;
        };
        if found != expected {
            return false;
        }
        cursor += found.len_utf8();
    }
    true
}

fn read_rope_text(rope: &ReactiveRope) -> String {
    let mut cursor = rope.cursor(0);
    let mut text = String::new();
    while let Some(chunk) = cursor.next_chunk() {
        text.push_str(chunk.text());
    }
    text
}

fn localize(range: TextRange, parent_start: usize) -> TextRange {
    TextRange::new(
        range.start.saturating_sub(parent_start),
        range.end.saturating_sub(parent_start),
    )
}

fn absolute_range(node: &TokenTreeNode, parent_start: usize) -> TextRange {
    TextRange::new(
        parent_start + node.local_range.start,
        parent_start + node.local_range.end,
    )
}

fn map_range(range: TextRange, delta: &EditDelta) -> TextRange {
    if range.end <= delta.range.start {
        return range;
    }
    if range.start >= delta.range.end {
        let shift = delta.net_byte_delta();
        return TextRange::new(
            range.start.saturating_add_signed(shift),
            range.end.saturating_add_signed(shift),
        );
    }
    TextRange::new(usize::MAX, usize::MAX)
}

fn can_reuse_node(
    old: &TokenTreeNode,
    old_parent_start: usize,
    parsed: &RawNode,
    old_text: &str,
    new_text: &str,
    delta: &EditDelta,
) -> bool {
    let old_abs = absolute_range(old, old_parent_start);
    old.kind == parsed.kind
        && map_range(old_abs, delta) == parsed.range
        && old_text.get(old_abs.start..old_abs.end)
            == new_text.get(parsed.range.start..parsed.range.end)
}

fn can_descend_into_node(old: &TokenTreeNode, parsed: &RawNode) -> bool {
    match (&old.kind, &parsed.kind) {
        (TokenTreeNodeKind::Body, TokenTreeNodeKind::Body) => true,
        (
            TokenTreeNodeKind::List {
                delimiter: left, ..
            },
            TokenTreeNodeKind::List {
                delimiter: right, ..
            },
        ) => left == right,
        (
            TokenTreeNodeKind::String {
                delimiter: left, ..
            },
            TokenTreeNodeKind::String {
                delimiter: right, ..
            },
        ) => left == right,
        (TokenTreeNodeKind::Symbol { text: left }, TokenTreeNodeKind::Symbol { text: right }) => {
            left == right
        }
        (TokenTreeNodeKind::Sigil { text: left }, TokenTreeNodeKind::Sigil { text: right }) => {
            left == right
        }
        (TokenTreeNodeKind::Number(left), TokenTreeNodeKind::Number(right)) => left == right,
        _ => false,
    }
}

fn count_nodes(node: &TokenTreeNode) -> usize {
    1 + node.children.iter().map(count_nodes).sum::<usize>()
}

fn number_body_valid(raw: &str, base: NumberBase, saw_radix: bool, suffix_start: usize) -> bool {
    let mut body = &raw[..suffix_start];
    if matches!(
        base,
        NumberBase::Binary | NumberBase::Octal | NumberBase::Decimal | NumberBase::Hexadecimal
    ) && body.starts_with("0")
        && body.len() >= 2
        && matches!(body.as_bytes()[1], b'b' | b'o' | b'd' | b'x')
    {
        body = &body[2..];
    }
    if body.is_empty() {
        return false;
    }
    for ch in body.chars() {
        if ch == '_' || (saw_radix && ch == '.') {
            continue;
        }
        if !is_digit_for_base(ch, base) {
            return false;
        }
    }
    true
}

fn is_digit_for_base(ch: char, base: NumberBase) -> bool {
    match base {
        NumberBase::Binary => matches!(ch, '0' | '1'),
        NumberBase::Octal => matches!(ch, '0'..='7'),
        NumberBase::Decimal => ch.is_ascii_digit(),
        NumberBase::Hexadecimal => ch.is_ascii_hexdigit(),
    }
}

fn parse_suffix(text: &str) -> Option<NumberSuffix> {
    match text {
        "" => None,
        "f32" => Some(NumberSuffix::F32),
        "f64" => Some(NumberSuffix::F64),
        "f" => Some(NumberSuffix::BigFloat),
        "d128" => Some(NumberSuffix::Decimal128),
        "d" => Some(NumberSuffix::BigDecimal),
        "u8" => Some(NumberSuffix::U8),
        "u16" => Some(NumberSuffix::U16),
        "u32" => Some(NumberSuffix::U32),
        "u64" => Some(NumberSuffix::U64),
        "u128" => Some(NumberSuffix::U128),
        "i8" => Some(NumberSuffix::I8),
        "i16" => Some(NumberSuffix::I16),
        "i32" => Some(NumberSuffix::I32),
        "i64" => Some(NumberSuffix::I64),
        "i128" => Some(NumberSuffix::I128),
        "u" => Some(NumberSuffix::BigUint),
        "i" => Some(NumberSuffix::BigInt),
        _ => None,
    }
}

impl fmt::Display for TokenTreeNodeKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Body => write!(f, "body"),
            Self::List { delimiter, .. } => write!(f, "{delimiter:?}"),
            Self::Symbol { text } => write!(f, "symbol({text})"),
            Self::Sigil { text } => write!(f, "sigil({text})"),
            Self::String { delimiter, .. } => write!(f, "string({delimiter:?})"),
            Self::Number(number) => write!(f, "number({})", number.raw),
            Self::Error { message } => write!(f, "error({message})"),
        }
    }
}

#[cfg(test)]
mod tests {
    use ruin_reactivity::Reactor;

    use super::*;
    use crate::RopeEdit;

    fn parse_text(text: &str) -> TokenTreeParse {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, text);
        TokenTreeParser::new().parse(&rope)
    }

    fn parse_with_rope(text: &str) -> (ReactiveRope, TokenTreeParse) {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, text);
        let parse = TokenTreeParser::new().parse(&rope);
        (rope, parse)
    }

    fn reparse_after_edit(
        before: &str,
        edit: RopeEdit,
    ) -> (ReactiveRope, TokenTreeParse, TokenTreeParse, EditDelta) {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, before);
        let mut parser = TokenTreeParser::new();
        let first = parser.parse(&rope);
        let delta = rope.edit(edit);
        let second = parser.reparse(&rope, &first, &delta);
        (rope, first, second, delta)
    }

    fn node_text<'a>(parse: &'a TokenTreeParse, node: &TokenTreeNode) -> &'a str {
        let range = parse
            .absolute_range(node.id)
            .expect("node should have absolute range");
        &parse.text()[range.start..range.end]
    }

    fn symbol_texts(parse: &TokenTreeParse) -> Vec<&str> {
        parse
            .root
            .children
            .iter()
            .filter_map(|node| match &node.kind {
                TokenTreeNodeKind::Symbol { text } => Some(text.as_str()),
                _ => None,
            })
            .collect()
    }

    fn sigil_texts(parse: &TokenTreeParse) -> Vec<&str> {
        parse
            .root
            .children
            .iter()
            .filter_map(|node| match &node.kind {
                TokenTreeNodeKind::Sigil { text } => Some(text.as_str()),
                _ => None,
            })
            .collect()
    }

    fn find_list_child(node: &TokenTreeNode, delimiter: DelimiterKind) -> &TokenTreeNode {
        node.children
            .iter()
            .find(|child| {
                matches!(
                    child.kind,
                    TokenTreeNodeKind::List {
                        delimiter: found,
                        ..
                    } if found == delimiter
                )
            })
            .expect("expected list child")
    }

    fn diagnostic_kinds(parse: &TokenTreeParse) -> Vec<TokenTreeDiagnosticKind> {
        parse
            .diagnostics
            .iter()
            .map(|diagnostic| diagnostic.kind)
            .collect()
    }

    #[test]
    fn empty_input_yields_empty_body() {
        let parse = parse_text("");
        assert!(parse.diagnostics.is_empty());
        assert!(parse.root.children.is_empty());
        assert_eq!(parse.root.kind, TokenTreeNodeKind::Body);
    }

    #[test]
    fn whitespace_only_input_yields_empty_body() {
        let parse = parse_text(" \n\t  ");
        assert!(parse.diagnostics.is_empty());
        assert!(parse.root.children.is_empty());
        assert!(parse.trivia.comments().is_empty());
    }

    #[test]
    fn parses_mixed_top_level_sequence_in_order() {
        let parse = parse_text("alpha + 12 \"hi\" [beta]");
        assert_eq!(parse.root.children.len(), 5);
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Sigil { .. }
        ));
        assert!(matches!(
            parse.root.children[2].kind,
            TokenTreeNodeKind::Number(..)
        ));
        assert!(matches!(
            parse.root.children[3].kind,
            TokenTreeNodeKind::String { .. }
        ));
        assert!(matches!(
            parse.root.children[4].kind,
            TokenTreeNodeKind::List { .. }
        ));
    }

    #[test]
    fn parses_nested_lists() {
        let parse = parse_text("{ foo(bar, [baz]) }");
        assert!(parse.diagnostics.is_empty());
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Brace,
                ..
            }
        ));
        let outer = &parse.root.children[0];
        assert!(matches!(
            outer.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        let inner_paren = find_list_child(outer, DelimiterKind::Parenthesis);
        assert!(matches!(
            inner_paren.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            inner_paren.children[1].kind,
            TokenTreeNodeKind::Sigil { .. }
        ));
        let inner_bracket = find_list_child(inner_paren, DelimiterKind::Bracket);
        assert!(matches!(
            inner_bracket.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
    }

    #[test]
    fn parses_empty_lists_for_all_delimiters() {
        let parse = parse_text("() [] {}");
        assert_eq!(parse.root.children.len(), 3);
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Parenthesis,
                terminated: true
            }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Bracket,
                terminated: true
            }
        ));
        assert!(matches!(
            parse.root.children[2].kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Brace,
                terminated: true
            }
        ));
    }

    #[test]
    fn unterminated_lists_report_diagnostics_per_delimiter() {
        let parse = parse_text("( [ {");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::UnterminatedList,
                TokenTreeDiagnosticKind::UnterminatedList,
                TokenTreeDiagnosticKind::UnterminatedList,
            ]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Parenthesis,
                terminated: false
            }
        ));
        let bracket = find_list_child(&parse.root.children[0], DelimiterKind::Bracket);
        assert!(matches!(
            bracket.kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Bracket,
                terminated: false
            }
        ));
    }

    #[test]
    fn unmatched_closing_delimiters_produce_error_nodes_and_continue() {
        let parse = parse_text(") foo ]");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::UnmatchedClosingDelimiter,
                TokenTreeDiagnosticKind::UnmatchedClosingDelimiter,
            ]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Error { .. }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            parse.root.children[2].kind,
            TokenTreeNodeKind::Error { .. }
        ));
    }

    #[test]
    fn mismatched_closer_inside_list_becomes_error_child() {
        let parse = parse_text("(foo]");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::UnmatchedClosingDelimiter,
                TokenTreeDiagnosticKind::UnterminatedList,
            ]
        );
        let list = &parse.root.children[0];
        assert!(matches!(
            list.kind,
            TokenTreeNodeKind::List {
                delimiter: DelimiterKind::Parenthesis,
                terminated: false
            }
        ));
        assert!(matches!(
            list.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            list.children[1].kind,
            TokenTreeNodeKind::Error { .. }
        ));
    }

    #[test]
    fn extra_closer_after_valid_list_is_reported_at_top_level() {
        let parse = parse_text("(foo))");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::UnmatchedClosingDelimiter]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::List { .. }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Error { .. }
        ));
    }

    #[test]
    fn symbols_accept_unicode_printable_text() {
        let parse = parse_text("alpha βeta café");
        assert_eq!(symbol_texts(&parse), vec!["alpha", "βeta", "café"]);
    }

    #[test]
    fn symbols_can_include_printable_non_sigil_punctuation() {
        let parse = parse_text("foo§bar baz_ß");
        assert_eq!(symbol_texts(&parse), vec!["foo§bar", "baz_ß"]);
    }

    #[test]
    fn symbols_split_around_sigils_and_delimiters() {
        let parse = parse_text("left+right(foo)");
        assert_eq!(symbol_texts(&parse), vec!["left", "right"]);
        assert_eq!(sigil_texts(&parse), vec!["+"]);
        assert!(matches!(
            parse.root.children[3].kind,
            TokenTreeNodeKind::List { .. }
        ));
    }

    #[test]
    fn numeric_prefix_does_not_start_symbol() {
        let parse = parse_text("1abc");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::InvalidNumber]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Error { .. }
        ));
    }

    #[test]
    fn every_defined_sigil_parses_as_a_sigil_node() {
        for sigil in SIGILS {
            let parse = parse_text(sigil);
            assert_eq!(
                parse.root.children.len(),
                1,
                "sigil `{sigil}` should be a single token"
            );
            assert!(
                matches!(
                    &parse.root.children[0].kind,
                    TokenTreeNodeKind::Sigil { text } if text == sigil
                ),
                "expected `{sigil}` to parse as sigil"
            );
        }
    }

    #[test]
    fn longest_match_sigils_win() {
        let parse = parse_text("a >>= b => c ..= d :: e");
        assert_eq!(sigil_texts(&parse), vec![">>=", "=>", "..=", "::"]);
    }

    #[test]
    fn comments_take_precedence_over_slash_sigils() {
        let parse = parse_text("a// comment\nb/* block */c");
        assert!(sigil_texts(&parse).is_empty());
        assert_eq!(symbol_texts(&parse), vec!["a", "b", "c"]);
        assert_eq!(parse.trivia.comments().len(), 2);
    }

    #[test]
    fn preserves_comments_as_trivia() {
        let parse = parse_text("foo // hi\n/* block */ bar");
        assert_eq!(parse.trivia.comments().len(), 2);
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
    }

    #[test]
    fn nested_block_comments_are_preserved_as_single_trivia_item() {
        let parse = parse_text("foo /* outer /* inner */ done */ bar");
        assert!(parse.diagnostics.is_empty());
        assert_eq!(parse.trivia.comments().len(), 1);
        assert_eq!(parse.trivia.comments()[0].kind, TriviaKind::BlockComment);
        assert_eq!(
            parse.trivia.comments()[0].text,
            "/* outer /* inner */ done */"
        );
    }

    #[test]
    fn unterminated_block_comment_reports_diagnostic_and_trivia() {
        let parse = parse_text("foo /* never closes");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::UnterminatedBlockComment]
        );
        assert_eq!(parse.trivia.comments().len(), 1);
        assert_eq!(parse.trivia.comments()[0].kind, TriviaKind::BlockComment);
    }

    #[test]
    fn trivia_queries_filter_by_range_and_offset() {
        let parse = parse_text("foo // hi\nbar /* block */ baz");
        let line = &parse.trivia.comments()[0];
        let block = &parse.trivia.comments()[1];
        let overlapping = parse
            .trivia
            .comments_in_range(TextRange::new(line.range.start, block.range.end));
        assert_eq!(overlapping.len(), 2);
        let before_block = parse.trivia.comments_before(block.range.start);
        assert_eq!(before_block.len(), 1);
        assert_eq!(before_block[0].text, "// hi");
    }

    #[test]
    fn comments_before_respects_exact_end_boundary() {
        let parse = parse_text("foo // one\nbar");
        let comment = &parse.trivia.comments()[0];
        let before_comment_end = parse.trivia.comments_before(comment.range.end);
        assert_eq!(before_comment_end.len(), 1);
        let before_comment_start = parse.trivia.comments_before(comment.range.start);
        assert!(before_comment_start.is_empty());
    }

    #[test]
    fn parses_empty_single_and_double_quoted_strings() {
        let parse = parse_text("'' \"\"");
        assert_eq!(parse.root.children.len(), 2);
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::String {
                delimiter: StringDelimiter::SingleQuote,
                terminated: true,
                ..
            }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::String {
                delimiter: StringDelimiter::DoubleQuote,
                terminated: true,
                ..
            }
        ));
    }

    #[test]
    fn quoted_strings_collect_text_fragments() {
        let parse = parse_text("\"hello\" 'world'");
        match &parse.root.children[0].kind {
            TokenTreeNodeKind::String { fragments, .. } => {
                assert_eq!(
                    fragments,
                    &vec![StringFragment::Text {
                        range: TextRange::new(1, 6),
                        text: "hello".into(),
                    }]
                );
            }
            other => panic!("expected string, got {other:?}"),
        }
        match &parse.root.children[1].kind {
            TokenTreeNodeKind::String { fragments, .. } => {
                assert_eq!(
                    fragments,
                    &vec![StringFragment::Text {
                        range: TextRange::new(9, 14),
                        text: "world".into(),
                    }]
                );
            }
            other => panic!("expected string, got {other:?}"),
        }
    }

    #[test]
    fn strings_support_valid_common_escapes() {
        let parse = parse_text("\"line\\nquote\\\"tab\\t\" '\\'' `tick\\``");
        assert!(parse.diagnostics.is_empty());
        assert_eq!(parse.root.children.len(), 3);
    }

    #[test]
    fn invalid_escape_sequences_report_diagnostics() {
        let parse = parse_text("\"bad\\q\" \"bad\\x41\"");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::InvalidEscape,
                TokenTreeDiagnosticKind::InvalidEscape,
            ]
        );
    }

    #[test]
    fn unicode_escape_sequences_validate_shape_and_digits() {
        let parse = parse_text("\"ok\\u{1f642}\" \"bad\\u{}\" \"bad\\u{zz}\" \"bad\\u1234\"");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::InvalidUnicodeEscape,
                TokenTreeDiagnosticKind::InvalidUnicodeEscape,
                TokenTreeDiagnosticKind::InvalidUnicodeEscape,
            ]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::String {
                terminated: true,
                ..
            }
        ));
    }

    #[test]
    fn unterminated_strings_report_diagnostic() {
        let parse = parse_text("\"unterminated");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::UnterminatedString]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::String {
                terminated: false,
                ..
            }
        ));
    }

    #[test]
    fn backtick_strings_support_multiple_interpolations() {
        let parse = parse_text("`a $(foo) b $(bar)`");
        let string = &parse.root.children[0];
        match &string.kind {
            TokenTreeNodeKind::String {
                delimiter: StringDelimiter::Backtick,
                terminated: true,
                fragments,
            } => {
                assert_eq!(fragments.len(), 4);
                assert_eq!(string.children.len(), 2);
                assert!(matches!(fragments[0], StringFragment::Text { .. }));
                assert!(matches!(
                    fragments[1],
                    StringFragment::Interpolation { child_index: 0, .. }
                ));
                assert!(matches!(fragments[2], StringFragment::Text { .. }));
                assert!(matches!(
                    fragments[3],
                    StringFragment::Interpolation { child_index: 1, .. }
                ));
                assert!(matches!(
                    string.children[0].kind,
                    TokenTreeNodeKind::List {
                        delimiter: DelimiterKind::Parenthesis,
                        terminated: true
                    }
                ));
                assert!(matches!(
                    string.children[1].kind,
                    TokenTreeNodeKind::List {
                        delimiter: DelimiterKind::Parenthesis,
                        terminated: true
                    }
                ));
            }
            other => panic!("expected backtick string, got {other:?}"),
        }
    }

    #[test]
    fn strings_support_interpolation() {
        let parse = parse_text("`Hello $(world)`");
        let string = &parse.root.children[0];
        match &string.kind {
            TokenTreeNodeKind::String { fragments, .. } => {
                assert_eq!(fragments.len(), 2);
                assert_eq!(string.children.len(), 1);
                assert!(matches!(
                    string.children[0].kind,
                    TokenTreeNodeKind::List { .. }
                ));
            }
            other => panic!("expected string, got {other:?}"),
        }
    }

    #[test]
    fn unterminated_interpolation_reports_both_relevant_diagnostics() {
        let parse = parse_text("`Hello $(world`");
        let kinds = diagnostic_kinds(&parse);
        assert_eq!(
            kinds,
            vec![
                TokenTreeDiagnosticKind::UnterminatedString,
                TokenTreeDiagnosticKind::UnterminatedList,
                TokenTreeDiagnosticKind::UnterminatedInterpolation,
                TokenTreeDiagnosticKind::UnterminatedString,
            ]
        );
        let string = &parse.root.children[0];
        assert!(matches!(
            string.kind,
            TokenTreeNodeKind::String {
                delimiter: StringDelimiter::Backtick,
                terminated: false,
                ..
            }
        ));
    }

    #[test]
    fn parses_numbers_with_supported_bases_and_suffixes() {
        let cases = [
            ("0", NumberBase::Decimal, false, None),
            (
                "12.3f64",
                NumberBase::Decimal,
                true,
                Some(NumberSuffix::F64),
            ),
            ("0d1_2_3", NumberBase::Decimal, false, None),
            (
                "0b1010u8",
                NumberBase::Binary,
                false,
                Some(NumberSuffix::U8),
            ),
            ("0o77i16", NumberBase::Octal, false, Some(NumberSuffix::I16)),
            (
                "0xffu",
                NumberBase::Hexadecimal,
                false,
                Some(NumberSuffix::BigUint),
            ),
            (
                "99d",
                NumberBase::Decimal,
                false,
                Some(NumberSuffix::BigDecimal),
            ),
            (
                "42d128",
                NumberBase::Decimal,
                false,
                Some(NumberSuffix::Decimal128),
            ),
            (
                "5f",
                NumberBase::Decimal,
                false,
                Some(NumberSuffix::BigFloat),
            ),
        ];

        for (text, base, has_radix_point, suffix) in cases {
            let parse = parse_text(text);
            assert!(
                parse.diagnostics.is_empty(),
                "unexpected diagnostics for `{text}`"
            );
            match &parse.root.children[0].kind {
                TokenTreeNodeKind::Number(number) => {
                    assert_eq!(number.raw, text);
                    assert_eq!(number.base, base);
                    assert_eq!(number.has_radix_point, has_radix_point);
                    assert_eq!(number.suffix, suffix);
                }
                other => panic!("expected number for `{text}`, got {other:?}"),
            }
        }
    }

    #[test]
    fn numbers_accept_current_underscore_placements() {
        let parse = parse_text("1_2_3 0x_ff 0d12_.3f64 1._5");
        assert!(parse.diagnostics.is_empty());
        assert_eq!(parse.root.children.len(), 4);
        assert!(
            parse
                .root
                .children
                .iter()
                .all(|node| matches!(node.kind, TokenTreeNodeKind::Number(..)))
        );
    }

    #[test]
    fn numbers_reject_integer_suffixes_on_radix_numbers() {
        let parse = parse_text("1.0u8");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::InvalidNumber]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Error { .. }
        ));
    }

    #[test]
    fn numbers_require_digits_after_base_prefix() {
        let parse = parse_text("0x 0b2 0o");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![
                TokenTreeDiagnosticKind::InvalidNumber,
                TokenTreeDiagnosticKind::InvalidNumber,
                TokenTreeDiagnosticKind::InvalidNumber,
            ]
        );
        assert!(
            parse
                .root
                .children
                .iter()
                .all(|node| matches!(node.kind, TokenTreeNodeKind::Error { .. }))
        );
    }

    #[test]
    fn numbers_reject_unknown_or_underscored_suffixes() {
        let parse = parse_text("12foo 9u_8");
        assert_eq!(
            diagnostic_kinds(&parse),
            vec![TokenTreeDiagnosticKind::InvalidNumber]
        );
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Error { .. }
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Number(..)
        ));
        assert!(matches!(
            parse.root.children[2].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
    }

    #[test]
    fn numbers_stop_before_non_numeric_sigils() {
        let parse = parse_text("0x1..2");
        assert_eq!(parse.root.children.len(), 3);
        assert!(matches!(
            parse.root.children[0].kind,
            TokenTreeNodeKind::Number(..)
        ));
        assert!(matches!(
            parse.root.children[1].kind,
            TokenTreeNodeKind::Sigil { ref text } if text == ".."
        ));
        assert!(matches!(
            parse.root.children[2].kind,
            TokenTreeNodeKind::Number(..)
        ));
    }

    #[test]
    fn diagnostics_capture_ranges_and_messages() {
        let parse = parse_text("\"bad\\x41\"");
        let diagnostic = &parse.diagnostics[0];
        assert_eq!(diagnostic.kind, TokenTreeDiagnosticKind::InvalidEscape);
        assert!(diagnostic.range.start < diagnostic.range.end);
        assert!(diagnostic.message.contains("hex escapes are not supported"));
    }

    #[test]
    fn absolute_range_and_span_track_nested_nodes() {
        let (rope, parse) = parse_with_rope("{\n  foo\n  [bar]\n}");
        let outer = &parse.root.children[0];
        let bracket = find_list_child(outer, DelimiterKind::Bracket);
        let range = parse
            .absolute_range(bracket.id)
            .expect("bracket should have range");
        assert_eq!(&parse.text()[range.start..range.end], "[bar]");
        let (start, end) = parse
            .absolute_span(&rope, bracket.id)
            .expect("span should exist");
        assert_eq!(start.line, 2);
        assert_eq!(start.column, 2);
        assert_eq!(end.line, 2);
        assert_eq!(end.column, 7);
    }

    #[test]
    fn node_text_uses_derived_absolute_ranges() {
        let parse = parse_text("{ alpha beta }");
        let list = &parse.root.children[0];
        let alpha = &list.children[0];
        let beta = &list.children[1];
        assert_eq!(node_text(&parse, alpha), "alpha");
        assert_eq!(node_text(&parse, beta), "beta");
    }

    #[test]
    fn reparsing_reuses_shifted_subtrees() {
        let (_rope, first, second, _delta) =
            reparse_after_edit("{ a b }", RopeEdit::insert(2, "long "));
        let original_b = first.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("original tree should contain symbol b")
            .id;
        let reparsed_b = second.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("reparsed tree should contain symbol b")
            .id;
        assert_eq!(original_b, reparsed_b);
    }

    #[test]
    fn reparsing_preserves_shifted_node_identity_but_updates_span() {
        let (rope, first, second, _delta) =
            reparse_after_edit("{\n  a\n  b\n}", RopeEdit::insert(2, "long "));
        let original_b = first.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("original tree should contain symbol b");
        let reparsed_b = second.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("reparsed tree should contain symbol b");
        assert_eq!(original_b.id, reparsed_b.id);
        let first_span = first
            .absolute_span(&rope, original_b.id)
            .expect("first span should exist");
        let second_span = second
            .absolute_span(&rope, reparsed_b.id)
            .expect("second span should exist");
        assert_ne!(first_span, second_span);
        assert_eq!(second_span.0.line, 2);
    }

    #[test]
    fn reparsing_reuses_unaffected_siblings_when_one_symbol_changes() {
        let (_rope, first, second, _delta) = reparse_after_edit(
            "{ left right }",
            RopeEdit::replace(TextRange::new(2, 6), "long"),
        );
        let first_list = &first.root.children[0];
        let second_list = &second.root.children[0];
        let old_right = first_list.children[1].id;
        let new_right = second_list.children[1].id;
        assert_eq!(old_right, new_right);
        assert_ne!(first.root.id, second.root.id);
        assert_ne!(first_list.id, second_list.id);
        assert_ne!(first_list.children[0].id, second_list.children[0].id);
    }

    #[test]
    fn reparsing_does_not_reuse_node_when_kind_changes() {
        let (_rope, first, second, _delta) =
            reparse_after_edit("foo", RopeEdit::replace(TextRange::new(0, 3), "123"));
        assert!(matches!(
            first.root.children[0].kind,
            TokenTreeNodeKind::Symbol { .. }
        ));
        assert!(matches!(
            second.root.children[0].kind,
            TokenTreeNodeKind::Number(..)
        ));
        assert_ne!(first.root.children[0].id, second.root.children[0].id);
    }

    #[test]
    fn reparsing_preserves_nodes_across_comment_and_whitespace_shifts() {
        let (_rope, first, second, _delta) =
            reparse_after_edit("{ alpha beta }", RopeEdit::insert(2, "/* hi */\n"));
        let first_beta = first.root.children[0].children[1].id;
        let second_beta = second.root.children[0].children[1].id;
        assert_eq!(first_beta, second_beta);
        assert_eq!(second.trivia.comments().len(), 1);
    }

    #[test]
    fn reactive_parser_updates_snapshot_and_reuses_subtrees() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "{ a b }");
        let parser = ReactiveTokenTree::new(&rope);

        let first = parser.snapshot();
        let original_b = first.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("original tree should contain symbol b")
            .id;

        let _ = rope.edit(RopeEdit::insert(2, "long "));

        let second = parser.snapshot();
        let reparsed_b = second.root.children[0]
            .children
            .iter()
            .find(|node| matches!(&node.kind, TokenTreeNodeKind::Symbol { text } if text == "b"))
            .expect("reparsed tree should contain symbol b")
            .id;
        assert_eq!(original_b, reparsed_b);
        let update = parser.last_update().expect("edit should produce update");
        assert!(update.stats.reused_nodes > 0);
    }

    #[test]
    fn reactive_parser_emits_update_events() {
        let reactor = Reactor::new();
        let rope = ReactiveRope::from_text(&reactor, "foo");
        let parser = ReactiveTokenTree::new(&rope);
        let seen = Rc::new(RefCell::new(Vec::new()));
        let _subscription = parser.updates().subscribe({
            let seen = Rc::clone(&seen);
            move |update| seen.borrow_mut().push(update.clone())
        });

        let _ = rope.edit(RopeEdit::replace(TextRange::new(0, 3), "123"));

        let seen = seen.borrow();
        assert_eq!(seen.len(), 1);
        assert!(seen[0].diagnostics_changed || seen[0].stats.total_nodes > 0);
    }
}
