//! Cypher Lexer.
//!
//! Tokenizes Cypher query strings into a stream of tokens.

use grafeo_common::utils::error::SourceSpan;

/// A token in the Cypher language.
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
    /// The token kind.
    pub kind: TokenKind,
    /// The source text.
    pub text: String,
    /// Source span.
    pub span: SourceSpan,
}

/// Token kinds in Cypher.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
    // Keywords
    /// MATCH
    Match,
    /// OPTIONAL
    Optional,
    /// WHERE
    Where,
    /// RETURN
    Return,
    /// WITH
    With,
    /// UNWIND
    Unwind,
    /// AS
    As,
    /// ORDER
    Order,
    /// BY
    By,
    /// ASC
    Asc,
    /// ASCENDING
    Ascending,
    /// DESC
    Desc,
    /// DESCENDING
    Descending,
    /// SKIP
    Skip,
    /// LIMIT
    Limit,
    /// CREATE
    Create,
    /// MERGE
    Merge,
    /// DELETE
    Delete,
    /// DETACH
    Detach,
    /// SET
    Set,
    /// REMOVE
    Remove,
    /// ON
    On,
    /// AND
    And,
    /// OR
    Or,
    /// XOR
    Xor,
    /// NOT
    Not,
    /// IN
    In,
    /// IS
    Is,
    /// NULL
    Null,
    /// TRUE
    True,
    /// FALSE
    False,
    /// CASE
    Case,
    /// WHEN
    When,
    /// THEN
    Then,
    /// ELSE
    Else,
    /// END
    End,
    /// DISTINCT
    Distinct,
    /// EXISTS
    Exists,
    /// COUNT
    Count,
    /// STARTS
    Starts,
    /// ENDS
    Ends,
    /// CONTAINS
    Contains,
    /// CALL
    Call,
    /// YIELD
    Yield,
    /// UNION
    Union,
    /// ALL
    All,

    // Literals
    /// Integer literal
    Integer,
    /// Float literal
    Float,
    /// String literal (single or double quoted)
    String,

    // Identifiers
    /// Identifier (unquoted)
    Identifier,
    /// Backtick-quoted identifier
    QuotedIdentifier,

    // Symbols
    /// (
    LParen,
    /// )
    RParen,
    /// [
    LBracket,
    /// ]
    RBracket,
    /// {
    LBrace,
    /// }
    RBrace,
    /// :
    Colon,
    /// ;
    Semicolon,
    /// ,
    Comma,
    /// .
    Dot,
    /// ..
    DotDot,
    /// |
    Pipe,
    /// $
    Dollar,

    // Operators
    /// =
    Eq,
    /// <>
    Ne,
    /// <
    Lt,
    /// <=
    Le,
    /// >
    Gt,
    /// >=
    Ge,
    /// +
    Plus,
    /// -
    Minus,
    /// *
    Star,
    /// /
    Slash,
    /// %
    Percent,
    /// ^
    Caret,
    /// +=
    PlusEq,
    /// =~
    RegexMatch,

    // Arrows
    /// ->
    Arrow,
    /// <-
    LeftArrow,
    /// --
    DoubleDash,

    // Special
    /// End of input
    Eof,
    /// Lexical error
    Error,
}

/// Cypher lexer.
#[derive(Clone)]
pub struct Lexer<'a> {
    /// Source text.
    source: &'a str,
    /// Current byte position.
    pos: usize,
    /// Start of current token.
    start: usize,
    /// Current line number.
    line: usize,
    /// Column of current line.
    column: usize,
}

impl<'a> Lexer<'a> {
    /// Creates a new lexer for the given source.
    pub fn new(source: &'a str) -> Self {
        Self {
            source,
            pos: 0,
            start: 0,
            line: 1,
            column: 1,
        }
    }

    /// Returns the next token.
    pub fn next_token(&mut self) -> Token {
        self.skip_whitespace_and_comments();

        self.start = self.pos;
        let start_line = self.line;
        let start_col = self.column;

        if self.is_at_end() {
            return self.make_token(TokenKind::Eof, start_line, start_col);
        }

        let ch = self.advance();

        let kind = match ch {
            '(' => TokenKind::LParen,
            ')' => TokenKind::RParen,
            '[' => TokenKind::LBracket,
            ']' => TokenKind::RBracket,
            '{' => TokenKind::LBrace,
            '}' => TokenKind::RBrace,
            ':' => TokenKind::Colon,
            ';' => TokenKind::Semicolon,
            ',' => TokenKind::Comma,
            '|' => TokenKind::Pipe,
            '$' => TokenKind::Dollar,
            '^' => TokenKind::Caret,
            '%' => TokenKind::Percent,
            '*' => TokenKind::Star,
            '/' => TokenKind::Slash,
            '.' => {
                if self.current_char() == '.' {
                    self.advance();
                    TokenKind::DotDot
                } else {
                    TokenKind::Dot
                }
            }
            '+' => {
                if self.current_char() == '=' {
                    self.advance();
                    TokenKind::PlusEq
                } else {
                    TokenKind::Plus
                }
            }
            '=' => {
                if self.current_char() == '~' {
                    self.advance();
                    TokenKind::RegexMatch
                } else {
                    TokenKind::Eq
                }
            }
            '<' => {
                if self.current_char() == '>' {
                    self.advance();
                    TokenKind::Ne
                } else if self.current_char() == '=' {
                    self.advance();
                    TokenKind::Le
                } else if self.current_char() == '-' {
                    self.advance();
                    TokenKind::LeftArrow
                } else {
                    TokenKind::Lt
                }
            }
            '>' => {
                if self.current_char() == '=' {
                    self.advance();
                    TokenKind::Ge
                } else {
                    TokenKind::Gt
                }
            }
            '-' => {
                if self.current_char() == '>' {
                    self.advance();
                    TokenKind::Arrow
                } else if self.current_char() == '-' {
                    self.advance();
                    TokenKind::DoubleDash
                } else {
                    TokenKind::Minus
                }
            }
            '\'' | '"' => self.scan_string(ch),
            '`' => self.scan_quoted_identifier(),
            _ if ch.is_ascii_digit() => self.scan_number(),
            _ if ch.is_ascii_alphabetic() || ch == '_' => self.scan_identifier(),
            _ => TokenKind::Error,
        };

        self.make_token(kind, start_line, start_col)
    }

    fn make_token(&self, kind: TokenKind, start_line: usize, start_col: usize) -> Token {
        Token {
            kind,
            text: self.source[self.start..self.pos].to_string(),
            span: SourceSpan::new(
                self.start,
                self.pos - self.start,
                start_line as u32,
                start_col as u32,
            ),
        }
    }

    fn skip_whitespace_and_comments(&mut self) {
        loop {
            match self.current_char() {
                ' ' | '\t' | '\r' => {
                    self.advance();
                }
                '\n' => {
                    self.advance();
                    self.line += 1;
                    self.column = 1;
                }
                '/' if self.peek_char() == '/' => {
                    // Line comment
                    while !self.is_at_end() && self.current_char() != '\n' {
                        self.advance();
                    }
                }
                '/' if self.peek_char() == '*' => {
                    // Block comment
                    self.advance(); // /
                    self.advance(); // *
                    while !self.is_at_end() {
                        if self.current_char() == '*' && self.peek_char() == '/' {
                            self.advance(); // *
                            self.advance(); // /
                            break;
                        }
                        if self.current_char() == '\n' {
                            self.line += 1;
                            self.column = 1;
                        }
                        self.advance();
                    }
                }
                _ => break,
            }
        }
    }

    fn scan_string(&mut self, quote: char) -> TokenKind {
        while !self.is_at_end() {
            let ch = self.current_char();
            if ch == quote {
                self.advance();
                return TokenKind::String;
            }
            if ch == '\\' {
                self.advance(); // Skip escape
                if !self.is_at_end() {
                    self.advance(); // Skip escaped char
                }
            } else if ch == '\n' {
                self.line += 1;
                self.column = 1;
                self.advance();
            } else {
                self.advance();
            }
        }
        TokenKind::Error // Unterminated string
    }

    fn scan_quoted_identifier(&mut self) -> TokenKind {
        while !self.is_at_end() && self.current_char() != '`' {
            if self.current_char() == '\n' {
                self.line += 1;
                self.column = 1;
            }
            self.advance();
        }
        if self.is_at_end() {
            return TokenKind::Error;
        }
        self.advance(); // Closing backtick
        TokenKind::QuotedIdentifier
    }

    fn scan_number(&mut self) -> TokenKind {
        while self.current_char().is_ascii_digit() {
            self.advance();
        }

        // Check for decimal point
        if self.current_char() == '.' && self.peek_char().is_ascii_digit() {
            self.advance(); // .
            while self.current_char().is_ascii_digit() {
                self.advance();
            }
            // Check for exponent
            if self.current_char() == 'e' || self.current_char() == 'E' {
                self.advance();
                if self.current_char() == '+' || self.current_char() == '-' {
                    self.advance();
                }
                while self.current_char().is_ascii_digit() {
                    self.advance();
                }
            }
            return TokenKind::Float;
        }

        // Check for exponent without decimal
        if self.current_char() == 'e' || self.current_char() == 'E' {
            self.advance();
            if self.current_char() == '+' || self.current_char() == '-' {
                self.advance();
            }
            while self.current_char().is_ascii_digit() {
                self.advance();
            }
            return TokenKind::Float;
        }

        TokenKind::Integer
    }

    fn scan_identifier(&mut self) -> TokenKind {
        while self.current_char().is_ascii_alphanumeric() || self.current_char() == '_' {
            self.advance();
        }

        let text = &self.source[self.start..self.pos];
        Self::keyword_kind(text).unwrap_or(TokenKind::Identifier)
    }

    fn keyword_kind(text: &str) -> Option<TokenKind> {
        use crate::query::keywords::CommonKeyword;

        let upper = text.to_uppercase();
        let upper = upper.as_str();

        // Try common keywords first (shared across parsers)
        if let Some(common) = CommonKeyword::from_uppercase(upper) {
            return Some(Self::map_common_keyword(common));
        }

        // Cypher-specific keywords
        match upper {
            "ASCENDING" => Some(TokenKind::Ascending),
            "DESCENDING" => Some(TokenKind::Descending),
            "XOR" => Some(TokenKind::Xor),
            "COUNT" => Some(TokenKind::Count),
            "UNION" => Some(TokenKind::Union),
            "ALL" => Some(TokenKind::All),
            _ => None,
        }
    }

    /// Maps a common keyword to the Cypher token kind.
    fn map_common_keyword(kw: crate::query::keywords::CommonKeyword) -> TokenKind {
        use crate::query::keywords::CommonKeyword;
        match kw {
            CommonKeyword::Match => TokenKind::Match,
            CommonKeyword::Return => TokenKind::Return,
            CommonKeyword::Where => TokenKind::Where,
            CommonKeyword::As => TokenKind::As,
            CommonKeyword::Distinct => TokenKind::Distinct,
            CommonKeyword::With => TokenKind::With,
            CommonKeyword::Optional => TokenKind::Optional,
            CommonKeyword::Order => TokenKind::Order,
            CommonKeyword::By => TokenKind::By,
            CommonKeyword::Asc => TokenKind::Asc,
            CommonKeyword::Desc => TokenKind::Desc,
            CommonKeyword::Limit => TokenKind::Limit,
            CommonKeyword::Skip => TokenKind::Skip,
            CommonKeyword::And => TokenKind::And,
            CommonKeyword::Or => TokenKind::Or,
            CommonKeyword::Not => TokenKind::Not,
            CommonKeyword::In => TokenKind::In,
            CommonKeyword::Is => TokenKind::Is,
            CommonKeyword::Null => TokenKind::Null,
            CommonKeyword::True => TokenKind::True,
            CommonKeyword::False => TokenKind::False,
            CommonKeyword::Create => TokenKind::Create,
            CommonKeyword::Delete => TokenKind::Delete,
            CommonKeyword::Set => TokenKind::Set,
            CommonKeyword::Remove => TokenKind::Remove,
            CommonKeyword::Merge => TokenKind::Merge,
            CommonKeyword::Detach => TokenKind::Detach,
            CommonKeyword::On => TokenKind::On,
            CommonKeyword::Call => TokenKind::Call,
            CommonKeyword::Yield => TokenKind::Yield,
            CommonKeyword::Exists => TokenKind::Exists,
            CommonKeyword::Unwind => TokenKind::Unwind,
            CommonKeyword::Starts => TokenKind::Starts,
            CommonKeyword::Ends => TokenKind::Ends,
            CommonKeyword::Contains => TokenKind::Contains,
            CommonKeyword::Case => TokenKind::Case,
            CommonKeyword::When => TokenKind::When,
            CommonKeyword::Then => TokenKind::Then,
            CommonKeyword::Else => TokenKind::Else,
            CommonKeyword::End => TokenKind::End,
            // Keywords in CommonKeyword but not used in Cypher
            // map to Identifier (they can appear as variable names)
            _ => TokenKind::Identifier,
        }
    }

    fn is_at_end(&self) -> bool {
        self.pos >= self.source.len()
    }

    fn current_char(&self) -> char {
        self.source[self.pos..].chars().next().unwrap_or('\0')
    }

    fn peek_char(&self) -> char {
        let mut chars = self.source[self.pos..].chars();
        chars.next();
        chars.next().unwrap_or('\0')
    }

    fn advance(&mut self) -> char {
        let ch = self.current_char();
        self.pos += ch.len_utf8();
        self.column += 1;
        ch
    }
}

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

    #[test]
    fn test_simple_tokens() {
        let mut lexer = Lexer::new("()[]{}:;,.|$");
        assert_eq!(lexer.next_token().kind, TokenKind::LParen);
        assert_eq!(lexer.next_token().kind, TokenKind::RParen);
        assert_eq!(lexer.next_token().kind, TokenKind::LBracket);
        assert_eq!(lexer.next_token().kind, TokenKind::RBracket);
        assert_eq!(lexer.next_token().kind, TokenKind::LBrace);
        assert_eq!(lexer.next_token().kind, TokenKind::RBrace);
        assert_eq!(lexer.next_token().kind, TokenKind::Colon);
        assert_eq!(lexer.next_token().kind, TokenKind::Semicolon);
        assert_eq!(lexer.next_token().kind, TokenKind::Comma);
        assert_eq!(lexer.next_token().kind, TokenKind::Dot);
        assert_eq!(lexer.next_token().kind, TokenKind::Pipe);
        assert_eq!(lexer.next_token().kind, TokenKind::Dollar);
        assert_eq!(lexer.next_token().kind, TokenKind::Eof);
    }

    #[test]
    fn test_keywords() {
        let mut lexer = Lexer::new("MATCH WHERE RETURN CREATE");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::Where);
        assert_eq!(lexer.next_token().kind, TokenKind::Return);
        assert_eq!(lexer.next_token().kind, TokenKind::Create);
    }

    #[test]
    fn test_case_insensitive_keywords() {
        let mut lexer = Lexer::new("match Match MATCH");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
    }

    #[test]
    fn test_numbers() {
        let mut lexer = Lexer::new("42 3.14 1e10 2.5e-3");
        assert_eq!(lexer.next_token().kind, TokenKind::Integer);
        assert_eq!(lexer.next_token().kind, TokenKind::Float);
        assert_eq!(lexer.next_token().kind, TokenKind::Float);
        assert_eq!(lexer.next_token().kind, TokenKind::Float);
    }

    #[test]
    fn test_strings() {
        let mut lexer = Lexer::new("'hello' \"world\"");
        let t1 = lexer.next_token();
        assert_eq!(t1.kind, TokenKind::String);
        assert_eq!(t1.text, "'hello'");

        let t2 = lexer.next_token();
        assert_eq!(t2.kind, TokenKind::String);
        assert_eq!(t2.text, "\"world\"");
    }

    #[test]
    fn test_arrows() {
        let mut lexer = Lexer::new("-> <- --");
        assert_eq!(lexer.next_token().kind, TokenKind::Arrow);
        assert_eq!(lexer.next_token().kind, TokenKind::LeftArrow);
        assert_eq!(lexer.next_token().kind, TokenKind::DoubleDash);
    }

    #[test]
    fn test_operators() {
        let mut lexer = Lexer::new("= <> < <= > >= + - * / % ^");
        assert_eq!(lexer.next_token().kind, TokenKind::Eq);
        assert_eq!(lexer.next_token().kind, TokenKind::Ne);
        assert_eq!(lexer.next_token().kind, TokenKind::Lt);
        assert_eq!(lexer.next_token().kind, TokenKind::Le);
        assert_eq!(lexer.next_token().kind, TokenKind::Gt);
        assert_eq!(lexer.next_token().kind, TokenKind::Ge);
        assert_eq!(lexer.next_token().kind, TokenKind::Plus);
        assert_eq!(lexer.next_token().kind, TokenKind::Minus);
        assert_eq!(lexer.next_token().kind, TokenKind::Star);
        assert_eq!(lexer.next_token().kind, TokenKind::Slash);
        assert_eq!(lexer.next_token().kind, TokenKind::Percent);
        assert_eq!(lexer.next_token().kind, TokenKind::Caret);
    }

    #[test]
    fn test_comments() {
        let mut lexer = Lexer::new("MATCH // comment\nRETURN");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::Return);
    }

    #[test]
    fn test_block_comments() {
        let mut lexer = Lexer::new("MATCH /* block\ncomment */ RETURN");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::Return);
    }

    #[test]
    fn test_quoted_identifier() {
        let mut lexer = Lexer::new("`my column`");
        let token = lexer.next_token();
        assert_eq!(token.kind, TokenKind::QuotedIdentifier);
        assert_eq!(token.text, "`my column`");
    }

    #[test]
    fn test_string_with_escape() {
        let mut lexer = Lexer::new(r#"'hello\'world'"#);
        let token = lexer.next_token();
        assert_eq!(token.kind, TokenKind::String);
    }

    #[test]
    fn test_dot_vs_dotdot() {
        let mut lexer = Lexer::new(". .. .");
        assert_eq!(lexer.next_token().kind, TokenKind::Dot);
        assert_eq!(lexer.next_token().kind, TokenKind::DotDot);
        assert_eq!(lexer.next_token().kind, TokenKind::Dot);
    }

    #[test]
    fn test_plus_eq() {
        let mut lexer = Lexer::new("+ += +");
        assert_eq!(lexer.next_token().kind, TokenKind::Plus);
        assert_eq!(lexer.next_token().kind, TokenKind::PlusEq);
        assert_eq!(lexer.next_token().kind, TokenKind::Plus);
    }

    #[test]
    fn test_regex_match() {
        let mut lexer = Lexer::new("= =~ =");
        assert_eq!(lexer.next_token().kind, TokenKind::Eq);
        assert_eq!(lexer.next_token().kind, TokenKind::RegexMatch);
        assert_eq!(lexer.next_token().kind, TokenKind::Eq);
    }

    #[test]
    fn test_all_logical_keywords() {
        let mut lexer = Lexer::new("AND OR XOR NOT IN IS");
        assert_eq!(lexer.next_token().kind, TokenKind::And);
        assert_eq!(lexer.next_token().kind, TokenKind::Or);
        assert_eq!(lexer.next_token().kind, TokenKind::Xor);
        assert_eq!(lexer.next_token().kind, TokenKind::Not);
        assert_eq!(lexer.next_token().kind, TokenKind::In);
        assert_eq!(lexer.next_token().kind, TokenKind::Is);
    }

    #[test]
    fn test_case_keywords() {
        let mut lexer = Lexer::new("CASE WHEN THEN ELSE END");
        assert_eq!(lexer.next_token().kind, TokenKind::Case);
        assert_eq!(lexer.next_token().kind, TokenKind::When);
        assert_eq!(lexer.next_token().kind, TokenKind::Then);
        assert_eq!(lexer.next_token().kind, TokenKind::Else);
        assert_eq!(lexer.next_token().kind, TokenKind::End);
    }

    #[test]
    fn test_string_keywords() {
        let mut lexer = Lexer::new("STARTS ENDS CONTAINS");
        assert_eq!(lexer.next_token().kind, TokenKind::Starts);
        assert_eq!(lexer.next_token().kind, TokenKind::Ends);
        assert_eq!(lexer.next_token().kind, TokenKind::Contains);
    }

    #[test]
    fn test_data_modification_keywords() {
        let mut lexer = Lexer::new("CREATE MERGE DELETE DETACH SET REMOVE");
        assert_eq!(lexer.next_token().kind, TokenKind::Create);
        assert_eq!(lexer.next_token().kind, TokenKind::Merge);
        assert_eq!(lexer.next_token().kind, TokenKind::Delete);
        assert_eq!(lexer.next_token().kind, TokenKind::Detach);
        assert_eq!(lexer.next_token().kind, TokenKind::Set);
        assert_eq!(lexer.next_token().kind, TokenKind::Remove);
    }

    #[test]
    fn test_ordering_keywords() {
        let mut lexer = Lexer::new("ORDER BY ASC ASCENDING DESC DESCENDING SKIP LIMIT");
        assert_eq!(lexer.next_token().kind, TokenKind::Order);
        assert_eq!(lexer.next_token().kind, TokenKind::By);
        assert_eq!(lexer.next_token().kind, TokenKind::Asc);
        assert_eq!(lexer.next_token().kind, TokenKind::Ascending);
        assert_eq!(lexer.next_token().kind, TokenKind::Desc);
        assert_eq!(lexer.next_token().kind, TokenKind::Descending);
        assert_eq!(lexer.next_token().kind, TokenKind::Skip);
        assert_eq!(lexer.next_token().kind, TokenKind::Limit);
    }

    #[test]
    fn test_misc_keywords() {
        let mut lexer =
            Lexer::new("OPTIONAL WITH UNWIND AS DISTINCT EXISTS COUNT CALL YIELD UNION ALL ON");
        assert_eq!(lexer.next_token().kind, TokenKind::Optional);
        assert_eq!(lexer.next_token().kind, TokenKind::With);
        assert_eq!(lexer.next_token().kind, TokenKind::Unwind);
        assert_eq!(lexer.next_token().kind, TokenKind::As);
        assert_eq!(lexer.next_token().kind, TokenKind::Distinct);
        assert_eq!(lexer.next_token().kind, TokenKind::Exists);
        assert_eq!(lexer.next_token().kind, TokenKind::Count);
        assert_eq!(lexer.next_token().kind, TokenKind::Call);
        assert_eq!(lexer.next_token().kind, TokenKind::Yield);
        assert_eq!(lexer.next_token().kind, TokenKind::Union);
        assert_eq!(lexer.next_token().kind, TokenKind::All);
        assert_eq!(lexer.next_token().kind, TokenKind::On);
    }

    #[test]
    fn test_literal_keywords() {
        let mut lexer = Lexer::new("NULL TRUE FALSE");
        assert_eq!(lexer.next_token().kind, TokenKind::Null);
        assert_eq!(lexer.next_token().kind, TokenKind::True);
        assert_eq!(lexer.next_token().kind, TokenKind::False);
    }

    #[test]
    fn test_identifier() {
        let mut lexer = Lexer::new("foo bar_baz x123");
        let t1 = lexer.next_token();
        assert_eq!(t1.kind, TokenKind::Identifier);
        assert_eq!(t1.text, "foo");

        let t2 = lexer.next_token();
        assert_eq!(t2.kind, TokenKind::Identifier);
        assert_eq!(t2.text, "bar_baz");

        let t3 = lexer.next_token();
        assert_eq!(t3.kind, TokenKind::Identifier);
        assert_eq!(t3.text, "x123");
    }

    #[test]
    fn test_integer_values() {
        let mut lexer = Lexer::new("0 123 999999");
        let t1 = lexer.next_token();
        assert_eq!(t1.kind, TokenKind::Integer);
        assert_eq!(t1.text, "0");

        let t2 = lexer.next_token();
        assert_eq!(t2.kind, TokenKind::Integer);
        assert_eq!(t2.text, "123");

        let t3 = lexer.next_token();
        assert_eq!(t3.kind, TokenKind::Integer);
        assert_eq!(t3.text, "999999");
    }

    #[test]
    fn test_float_values() {
        let mut lexer = Lexer::new("3.14 0.5 1e10 2e-5 3.14e+2");
        let t1 = lexer.next_token();
        assert_eq!(t1.kind, TokenKind::Float);
        assert_eq!(t1.text, "3.14");

        let t2 = lexer.next_token();
        assert_eq!(t2.kind, TokenKind::Float);
        assert_eq!(t2.text, "0.5");

        let t3 = lexer.next_token();
        assert_eq!(t3.kind, TokenKind::Float);
        assert_eq!(t3.text, "1e10");

        let t4 = lexer.next_token();
        assert_eq!(t4.kind, TokenKind::Float);
        assert_eq!(t4.text, "2e-5");

        let t5 = lexer.next_token();
        assert_eq!(t5.kind, TokenKind::Float);
        assert_eq!(t5.text, "3.14e+2");
    }

    #[test]
    fn test_whitespace_handling() {
        let mut lexer = Lexer::new("  MATCH\t\t(n)\n\nRETURN  ");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::LParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::RParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Return);
        assert_eq!(lexer.next_token().kind, TokenKind::Eof);
    }

    #[test]
    fn test_complex_query() {
        let mut lexer =
            Lexer::new("MATCH (n:Person)-[:KNOWS*1..3]->(m) WHERE n.age > 30 RETURN n.name");
        assert_eq!(lexer.next_token().kind, TokenKind::Match);
        assert_eq!(lexer.next_token().kind, TokenKind::LParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Colon);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::RParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Minus);
        assert_eq!(lexer.next_token().kind, TokenKind::LBracket);
        assert_eq!(lexer.next_token().kind, TokenKind::Colon);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Star);
        assert_eq!(lexer.next_token().kind, TokenKind::Integer);
        assert_eq!(lexer.next_token().kind, TokenKind::DotDot);
        assert_eq!(lexer.next_token().kind, TokenKind::Integer);
        assert_eq!(lexer.next_token().kind, TokenKind::RBracket);
        assert_eq!(lexer.next_token().kind, TokenKind::Arrow);
        assert_eq!(lexer.next_token().kind, TokenKind::LParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::RParen);
        assert_eq!(lexer.next_token().kind, TokenKind::Where);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Dot);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Gt);
        assert_eq!(lexer.next_token().kind, TokenKind::Integer);
        assert_eq!(lexer.next_token().kind, TokenKind::Return);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Dot);
        assert_eq!(lexer.next_token().kind, TokenKind::Identifier);
        assert_eq!(lexer.next_token().kind, TokenKind::Eof);
    }

    #[test]
    fn test_error_invalid_char() {
        let mut lexer = Lexer::new("@");
        assert_eq!(lexer.next_token().kind, TokenKind::Error);
    }

    #[test]
    fn test_unterminated_string() {
        let mut lexer = Lexer::new("'hello");
        assert_eq!(lexer.next_token().kind, TokenKind::Error);
    }

    #[test]
    fn test_unterminated_quoted_identifier() {
        let mut lexer = Lexer::new("`column");
        assert_eq!(lexer.next_token().kind, TokenKind::Error);
    }

    #[test]
    fn test_span_tracking() {
        let mut lexer = Lexer::new("MATCH (n)");
        let t1 = lexer.next_token();
        assert_eq!(t1.kind, TokenKind::Match);
        assert_eq!(t1.span.line, 1);
        assert_eq!(t1.span.column, 1);

        let t2 = lexer.next_token();
        assert_eq!(t2.kind, TokenKind::LParen);
        // Just verify the span was created (line/column should advance)
        assert!(t2.span.column > t1.span.column);
    }

    #[test]
    fn test_multiline_span() {
        let mut lexer = Lexer::new("MATCH\n(n)");
        let _ = lexer.next_token(); // MATCH
        let t2 = lexer.next_token(); // (
        assert_eq!(t2.span.line, 2);
        assert_eq!(t2.span.column, 1);
    }
}
