pub mod cursor;
pub mod scan_error;
mod scan_error_ext;
pub mod token;

pub use scan_error::{ScanError, ScanErrorKind};
pub use token::{Token, TokenKind, Tokens};

use crate::{Location, Source};
use cursor::Cursor;
use scan_error_ext::ScanErrorExt;
use std::{collections::HashMap, sync::LazyLock};

type TokenMap = HashMap<&'static str, TokenKind>;

/// Initialize a token map using 'key => value' notation.
macro_rules! token_map {
    ($($key:expr => $value:expr),+ $(,)?) => {
        {
            let mut map: TokenMap = HashMap::new();
            $(map.insert($key, $value);)+
            map
        }
    }
}

/// Nexus lexical scanner.
pub struct Scanner<'a> {
    comment_nesting_level: u32, // Tracks nested (multi-line) comment level.
    source: Source<'a>,
    cursor: Cursor<'a>,
    tokens: Tokens,
}

impl<'a> Scanner<'a> {
    /// Create a new scanner from a source string.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::{Scanner, Source};
    ///
    /// let source = Source::new("fn main() {}");
    /// let scanner = Scanner::new(source);
    /// ```
    ///
    #[must_use]
    pub fn new(source: Source<'a>) -> Self {
        Self {
            comment_nesting_level: 0,
            source,
            cursor: Cursor::new(source.code()),
            tokens: Vec::new(),
        }
    }

    /// Scan the source string and generate a list of tokens.
    ///
    /// # Errors
    ///
    /// This function will return a `ScanError` if the source string can not be parsed properly.
    ///
    /// Note: The current implementation will bail out at the first error it encounters.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::{Scanner, Source};
    ///
    /// let source = Source::new("fn main() {}");
    /// let scanner = Scanner::new(source);
    /// let tokens = scanner.scan().expect("Scanning failed.");
    /// ```
    ///
    pub fn scan(mut self) -> Result<Tokens, ScanError> {
        while let Some(c) = self.cursor.value() {
            if self.comment_nesting_level > 0 {
                match c {
                    '/' if self.cursor.peek() == Some('*') => {
                        // Nested comment start.
                        self.comment_nesting_level += 1;
                        self.cursor.advance(); // Skip the '*'.
                    }
                    '*' if self.cursor.peek() == Some('/') => {
                        // Comment end.
                        self.comment_nesting_level -= 1;
                        self.cursor.advance(); // Skip the '/'.
                    }
                    _ => {}
                }

                self.cursor.advance();
                continue;
            }

            match c {
                '(' => self.push_token(TokenKind::LParen, "("),
                ')' => self.push_token(TokenKind::RParen, ")"),
                '{' => self.push_token(TokenKind::LBrace, "{"),
                '}' => self.push_token(TokenKind::RBrace, "}"),
                '[' => self.push_token(TokenKind::LBracket, "["),
                ']' => self.push_token(TokenKind::RBracket, "]"),
                ':' => self.push_token(TokenKind::Colon, ":"),
                ';' => self.push_token(TokenKind::Semicolon, ";"),
                '&' => self.peek_match(('&', TokenKind::And, "&&"), (TokenKind::Amp, "&")),
                '+' => self.peek_match(('=', TokenKind::PlusIs, "+="), (TokenKind::Plus, "+")),
                '-' => match self.cursor.peek() {
                    Some('>') => {
                        self.cursor.advance();
                        self.push_token(TokenKind::Arrow, "->");
                    }
                    Some('=') => {
                        self.cursor.advance();
                        self.push_token(TokenKind::MinusIs, "-=");
                    }
                    _ => self.push_token(TokenKind::Minus, "-"),
                },
                '*' => self.peek_match(('=', TokenKind::StarIs, "*="), (TokenKind::Star, "*")),
                '/' => {
                    match self.cursor.peek() {
                        Some('/') => {
                            self.cursor.advance_while(|c| c != '\n');
                            continue;
                        }
                        Some('*') => {
                            self.comment_nesting_level = 1;
                            self.cursor.advance();
                            // Skip the normal cursor advancement to avoid double-advancing.
                            continue;
                        }
                        Some('=') => {
                            self.cursor.advance();
                            self.push_token(TokenKind::SlashIs, "/=");
                        }
                        _ => self.push_token(TokenKind::Slash, "/"),
                    }
                }
                '\\' => self.push_token(TokenKind::Backslash, "\\"),
                '%' => {
                    self.peek_match(('=', TokenKind::PercentIs, "%="), (TokenKind::Percent, "%"));
                }
                ',' => self.push_token(TokenKind::Comma, ","),
                '.' => match self.cursor.peek() {
                    Some('.') => {
                        self.cursor.advance();
                        if self.cursor.peek() == Some('=') {
                            self.cursor.advance();
                            self.push_token(TokenKind::RangeInclusive, "..=");
                        } else {
                            self.push_token(TokenKind::Range, "..");
                        }
                    }
                    _ => self.push_token(TokenKind::Dot, "."),
                },
                '_' => self.push_token(TokenKind::Underscore, "_"),
                '=' => self.peek_match(('=', TokenKind::Eq, "=="), (TokenKind::Is, "=")),
                '>' => self.peek_match(('=', TokenKind::GtEq, ">="), (TokenKind::Gt, ">")),
                '<' => self.peek_match(('=', TokenKind::LtEq, "<="), (TokenKind::Lt, "<")),
                '!' => self.peek_match(('=', TokenKind::NotEq, "!="), (TokenKind::Bang, "!")),
                '|' => match self.cursor.peek() {
                    Some('|') => {
                        self.cursor.advance();
                        self.push_token(TokenKind::DoublePipe, "||");
                    }
                    _ => self.push_token(TokenKind::Pipe, "|"),
                },
                '"' => {
                    self.parse_string()?;
                    // Skip the normal cursor advancement since parse_string already consumes the closing quote.
                    continue;
                }
                c if c.is_ascii_digit() => self.parse_number()?,
                c if c.is_alphabetic() => self.parse_alphabetic(),
                c if c.is_whitespace() => {
                    // Whitespace is ignored, cursor will advance below
                }
                _ => return Err(self.error(ScanErrorKind::UnexpectedCharacter(c))),
            }

            self.cursor.advance();
        }

        Ok(self.tokens)
    }

    /// Parse string literal.
    fn parse_string(&mut self) -> Result<(), ScanError> {
        let mut string_value = String::new();
        let start_index = self.cursor.index();
        let start_loc = self.cursor.location();

        // Skip opening quote.
        self.cursor.consume();

        while let Some(c) = self.cursor.consume() {
            match c {
                '"' => {
                    // Found closing quote - capture the original lexeme including quotes.
                    let end_index = self.cursor.index();
                    let lexeme = &self.source.code()[start_index..end_index];
                    self.push_token_at(TokenKind::String(string_value), lexeme, start_loc);
                    return Ok(());
                }
                '\\' => {
                    // Handle escape sequences.
                    if let Some(escape_char) = self.cursor.consume() {
                        match escape_char {
                            '"' => string_value.push('"'),
                            '\\' => string_value.push('\\'),
                            'n' => string_value.push('\n'),
                            'r' => string_value.push('\r'),
                            't' => string_value.push('\t'),
                            'b' => string_value.push('\u{0008}'), // backspace
                            'f' => string_value.push('\u{000C}'), // form feed
                            '0' => string_value.push('\0'),
                            'x' => {
                                // Parse \x followed by 2 hex digits.
                                let hex_value = self.parse_hex_escape(2)?;
                                if let Some(ch) = char::from_u32(hex_value) {
                                    string_value.push(ch);
                                } else {
                                    return Err(
                                        self.error(ScanErrorKind::InvalidEscapeSequence('x'))
                                    );
                                }
                            }
                            'u' => {
                                // Parse \u followed by 4 hex digits.
                                let hex_value = self.parse_hex_escape(4)?;
                                if let Some(ch) = char::from_u32(hex_value) {
                                    string_value.push(ch);
                                } else {
                                    return Err(
                                        self.error(ScanErrorKind::InvalidEscapeSequence('u'))
                                    );
                                }
                            }
                            'U' => {
                                // Parse \U followed by 8 hex digits.
                                let hex_value = self.parse_hex_escape(8)?;
                                if let Some(ch) = char::from_u32(hex_value) {
                                    string_value.push(ch);
                                } else {
                                    return Err(
                                        self.error(ScanErrorKind::InvalidEscapeSequence('U'))
                                    );
                                }
                            }
                            _ => {
                                return Err(
                                    self.error(ScanErrorKind::InvalidEscapeSequence(escape_char))
                                );
                            }
                        }
                    } else {
                        return Err(self.error(ScanErrorKind::IncompleteEscapeSequence('\\')));
                    }
                }
                '\n' | '\r' => {
                    return Err(self.error(ScanErrorKind::UnterminatedStringLiteral(string_value)));
                }
                _ => {
                    // Regular character.
                    string_value.push(c);
                }
            }
        }

        // If we reach here, the string was not terminated.
        Err(self.error(ScanErrorKind::UnterminatedStringLiteral(string_value)))
    }

    /// Parse hex escape sequence with specified number of digits.
    fn parse_hex_escape(&mut self, num_digits: usize) -> Result<u32, ScanError> {
        let mut hex_value = 0u32;

        for _ in 0..num_digits {
            if let Some(hex_char) = self.cursor.consume() {
                if let Some(digit) = hex_char.to_digit(16) {
                    hex_value = hex_value * 16 + digit;
                } else {
                    return Err(self.error(ScanErrorKind::InvalidHexDigit(hex_char)));
                }
            } else {
                let escape_type = match num_digits {
                    2 => 'x',
                    4 => 'u',
                    8 => 'U',
                    _ => '?',
                };
                return Err(self.error(ScanErrorKind::IncompleteEscapeSequence(escape_type)));
            }
        }

        Ok(hex_value)
    }

    /// Parse number literal.
    fn parse_number(&mut self) -> Result<(), ScanError> {
        // Save the starting location before consuming the token.
        let start_loc = self.cursor.location();

        let first_char = self.cursor.value().unwrap(); // NOTE: The cursor was peeked before.
        let mut s = first_char.to_string(); // Stripped (no underscores); used for f64 parsing.
        let mut raw = first_char.to_string(); // Raw literal including underscores; used for errors.

        let mut has_dot = false;
        let mut prev_was_underscore = false;
        let mut fractional_digit_seen = false;

        while let Some(p) = self.cursor.peek() {
            if p.is_ascii_digit() {
                self.cursor.advance();
                let c = self.cursor.value().unwrap(); // NOTE: The cursor was peeked before.
                s.push(c);
                raw.push(c);
                prev_was_underscore = false;
                if has_dot {
                    fractional_digit_seen = true;
                }
            } else if p == '_' {
                self.cursor.advance();
                raw.push('_');
                if prev_was_underscore || (has_dot && !fractional_digit_seen) {
                    // Adjacent underscores (e.g. `1__0`) or underscore as first fractional char (e.g. `1._0`).
                    return Err(self.error(ScanErrorKind::InvalidNumberUnderscore(raw)));
                }
                prev_was_underscore = true;
            } else if p == '.' && !has_dot {
                if prev_was_underscore {
                    // Trailing underscore before decimal point (e.g. `1_.5`).
                    return Err(self.error(ScanErrorKind::InvalidNumberUnderscore(raw)));
                }
                has_dot = true;
                self.cursor.advance();
                let c = self.cursor.value().unwrap(); // NOTE: The cursor was peeked before.
                s.push(c);
                raw.push(c);
            } else {
                break;
            }
        }

        if prev_was_underscore {
            // Trailing underscore at end of literal (e.g. `1_`).
            return Err(self.error(ScanErrorKind::InvalidNumberUnderscore(raw)));
        }

        if s.ends_with('.') {
            match self.cursor.peek() {
                Some('.') => {
                    // Range token ('..') or inclusive range ('..='); remove trailing dot and advance cursor.
                    s.pop();
                    self.cursor.advance();

                    // NOTE: We know we're parsing a digit, so unwrapping is fine.
                    self.push_token_at(TokenKind::Number(s.parse().unwrap()), s, start_loc);

                    if self.cursor.peek() == Some('=') {
                        self.cursor.advance();
                        self.push_token(TokenKind::RangeInclusive, "..=");
                    } else {
                        self.push_token(TokenKind::Range, "..");
                    }
                }
                _ => {
                    return Err(self.error(ScanErrorKind::MalformedNumberLiteral(raw)));
                }
            }
        } else {
            // NOTE: We know we're parsing a digit, so unwrapping is fine.
            self.push_token_at(TokenKind::Number(s.parse().unwrap()), s, start_loc);
        }

        Ok(())
    }

    /// Parse keyword/identifier.
    fn parse_alphabetic(&mut self) {
        static KEYWORDS: LazyLock<TokenMap> = LazyLock::new(|| {
            token_map! {
                "true"     => TokenKind::True,
                "false"    => TokenKind::False,
                "assert"   => TokenKind::Assert,
                "const"    => TokenKind::Const,
                "let"      => TokenKind::Let,
                "mut"      => TokenKind::Mut,
                "fn"       => TokenKind::Function,
                "if"       => TokenKind::If,
                "else"     => TokenKind::Else,
                "for"      => TokenKind::For,
                "in"       => TokenKind::In,
                "loop"     => TokenKind::Loop,
                "while"    => TokenKind::While,
                "break"    => TokenKind::Break,
                "continue" => TokenKind::Continue,
                "return"   => TokenKind::Return,
                "use"      => TokenKind::Use,
                "bool"     => TokenKind::BoolId,
                "Node"     => TokenKind::NodeId,
                "Group"    => TokenKind::GroupId,
                "Number"   => TokenKind::NumberId,
                "PortRef"  => TokenKind::PortRefId,
                "String"   => TokenKind::StringId,
                "port"     => TokenKind::Port,
                "print"    => TokenKind::Print,
                "node"     => TokenKind::Node,
                "group"    => TokenKind::Group,
                "type"     => TokenKind::Type,
                "out"      => TokenKind::Out,
                "inout"    => TokenKind::Inout,
                "self"     => TokenKind::Self_,
            }
        });

        // Save the starting location before consuming the token.
        let start_loc = self.cursor.location();

        // NOTE: The cursor value was peeked before we arrive here.
        let mut s = self.cursor.value().unwrap().to_string();

        // Eagerly consume all consecutive alphanumeric characters to form an identifier.
        while let Some(p) = self.cursor.peek() {
            if p.is_alphanumeric() || p == '_' {
                self.cursor.advance();
                // NOTE: The cursor value was peeked before advancing.
                s.push(self.cursor.value().unwrap());
            } else {
                break;
            }
        }

        if let Some(t) = KEYWORDS.get(s.as_str()) {
            self.push_token_at(t.clone(), s, start_loc);
        } else {
            self.push_token_at(TokenKind::Identifier(s.clone()), s, start_loc);
        }
    }

    /// Append a token to the internal token stream.
    fn push_token(&mut self, kind: TokenKind, lexeme: impl Into<String>) {
        self.tokens.push(Token {
            kind,
            lexeme: lexeme.into(),
            loc: self.cursor.location(),
        });
    }

    /// Append a token to the internal token stream with a specific location.
    fn push_token_at(&mut self, kind: TokenKind, lexeme: impl Into<String>, loc: Location) {
        self.tokens.push(Token {
            kind,
            lexeme: lexeme.into(),
            loc,
        });
    }

    /// Helper for matching on peek with a default token fallback.
    ///
    /// Handles common pattern of checking next character and either:
    ///
    /// - Advancing cursor and returning a compound token if it matches,
    /// - Or just returning a simple token if it doesn't match.
    ///
    fn peek_match(&mut self, compound: (char, TokenKind, &str), default: (TokenKind, &str)) {
        let (peek_char, compound_kind, compound_lexeme) = compound;
        let (default_kind, default_lexeme) = default;

        match self.cursor.peek() {
            Some(c) if c == peek_char => {
                self.push_token(compound_kind, compound_lexeme);
                self.cursor.advance();
            }
            _ => self.push_token(default_kind, default_lexeme),
        }
    }
}

impl ScanErrorExt for Scanner<'_> {
    fn error(&self, kind: ScanErrorKind) -> ScanError {
        ScanError::new(self.source.into(), &self.cursor.location(), kind)
    }
}

#[cfg(test)]
mod tests {
    use super::{Scanner, TokenKind};
    use pretty_assertions::assert_eq;

    /// Test a single token.
    #[track_caller]
    #[allow(clippy::needless_pass_by_value)] // Callers pass owned literals; not worth &-ing ~100 call sites.
    fn test_single(source: &str, expected: TokenKind) {
        let scanner = Scanner::new(source.into());
        let tokens = scanner.scan().expect("Scanning failed.");

        assert_eq!(tokens.len(), 1);

        let token = tokens
            .first()
            .expect("Expected at least one token, got none");

        assert_eq!(token.kind, expected);
        assert_eq!(token.lexeme, source); // We only test a single token.
    }

    /// Test multiple tokens.
    #[track_caller]
    fn test_multiple(source: &str, expected: &[TokenKind]) {
        let scanner = Scanner::new(source.into());
        let tokens = scanner.scan().expect("Scanning failed.");

        // Check token count match.
        assert_eq!(
            tokens.len(),
            expected.len(),
            "\nToken count mismatch for source: '{}'\nExpected {} tokens: {:?}\nGot      {} tokens: {:?}",
            source,
            expected.len(),
            expected,
            tokens.len(),
            tokens.iter().map(|t| &t.kind).collect::<Vec<_>>()
        );

        // Check each token matches expected.
        for (i, (token, expected_token)) in tokens.into_iter().zip(expected.iter()).enumerate() {
            assert_eq!(
                token.kind, *expected_token,
                "\nToken mismatch at position {} for source: '{}'\nExpected: {:?}\nActual: {:?}\nFull expected tokens: {:?}",
                i, source, expected_token, token.kind, expected
            );
        }
    }

    /// Test that scanning results in an error.
    #[track_caller]
    fn test_error(source: &str) {
        let scanner = Scanner::new(source.into());
        let result = scanner.scan();

        if let Ok(tokens) = &result {
            panic!(
                "Expected scan error for input '{source}', but scan succeeded with tokens: {:?}",
                tokens.iter().map(|t| &t.kind).collect::<Vec<_>>()
            );
        }
    }

    #[test]
    fn basic_single_token() {
        test_single("(", TokenKind::LParen);
        test_single(")", TokenKind::RParen);
        test_single("{", TokenKind::LBrace);
        test_single("}", TokenKind::RBrace);
        test_single("[", TokenKind::LBracket);
        test_single("]", TokenKind::RBracket);
        test_single(":", TokenKind::Colon);
        test_single(";", TokenKind::Semicolon);
        test_single("&", TokenKind::Amp);
        test_single("+", TokenKind::Plus);
        test_single("-", TokenKind::Minus);
        test_single("->", TokenKind::Arrow);
        test_single("*", TokenKind::Star);
        test_single("/", TokenKind::Slash);
        test_single("\\", TokenKind::Backslash);
        test_single("%", TokenKind::Percent);
        test_single(",", TokenKind::Comma);
        test_single(".", TokenKind::Dot);
        test_single("..", TokenKind::Range);
        test_single("_", TokenKind::Underscore);
        test_single("=", TokenKind::Is);
        test_single("==", TokenKind::Eq);
        test_single(">", TokenKind::Gt);
        test_single(">=", TokenKind::GtEq);
        test_single("<", TokenKind::Lt);
        test_single("<=", TokenKind::LtEq);
        test_single("!", TokenKind::Bang);
        test_single("!=", TokenKind::NotEq);
        test_single("&&", TokenKind::And);
        test_single("||", TokenKind::DoublePipe);
        test_single("|", TokenKind::Pipe);
    }

    #[test]
    fn basic_keyword() {
        test_single("true", TokenKind::True);
        test_single("false", TokenKind::False);
        test_single("const", TokenKind::Const);
        test_single("let", TokenKind::Let);
        test_single("mut", TokenKind::Mut);
        test_single("fn", TokenKind::Function);
        test_single("if", TokenKind::If);
        test_single("else", TokenKind::Else);
        test_single("for", TokenKind::For);
        test_single("in", TokenKind::In);
        test_single("while", TokenKind::While);
        test_single("return", TokenKind::Return);
        test_single("use", TokenKind::Use);
        test_single("bool", TokenKind::BoolId);
        test_single("Node", TokenKind::NodeId);
        test_single("Group", TokenKind::GroupId);
        test_single("Number", TokenKind::NumberId);
        test_single("String", TokenKind::StringId);
        test_single("port", TokenKind::Port);
        test_single("print", TokenKind::Print);
        test_single("node", TokenKind::Node);
        test_single("group", TokenKind::Group);
        test_single("self", TokenKind::Self_);
    }

    #[test]
    fn self_keyword() {
        // 'self' always scans as Self_, regardless of position.
        test_single("self", TokenKind::Self_);

        // Member declaration: self.name = node Foo;
        test_multiple(
            "self.src = node Source;",
            &[
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("src".to_owned()),
                TokenKind::Is,
                TokenKind::Node,
                TokenKind::Identifier("Source".to_owned()),
                TokenKind::Semicolon,
            ],
        );

        // Member access for a port reference: self.src.output
        test_multiple(
            "self.src.output",
            &[
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("src".to_owned()),
                TokenKind::Dot,
                TokenKind::Identifier("output".to_owned()),
            ],
        );

        // Connection with self on both sides: self.a.out -> self.b.in;
        test_multiple(
            "self.src.output -> self.sink.input;",
            &[
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("src".to_owned()),
                TokenKind::Dot,
                TokenKind::Identifier("output".to_owned()),
                TokenKind::Arrow,
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("sink".to_owned()),
                TokenKind::Dot,
                TokenKind::Identifier("input".to_owned()),
                TokenKind::Semicolon,
            ],
        );

        // self inside a group body alongside ordinary let bindings.
        test_multiple(
            "group { self.n = node Foo; let x = 1; }",
            &[
                TokenKind::Group,
                TokenKind::LBrace,
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("n".to_owned()),
                TokenKind::Is,
                TokenKind::Node,
                TokenKind::Identifier("Foo".to_owned()),
                TokenKind::Semicolon,
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Is,
                TokenKind::Number(1.0),
                TokenKind::Semicolon,
                TokenKind::RBrace,
            ],
        );

        // 'self' outside a group body still scans as Self_ (contextual restriction is semantic).
        test_multiple(
            "let p = self.src;",
            &[
                TokenKind::Let,
                TokenKind::Identifier("p".to_owned()),
                TokenKind::Is,
                TokenKind::Self_,
                TokenKind::Dot,
                TokenKind::Identifier("src".to_owned()),
                TokenKind::Semicolon,
            ],
        );
    }

    #[test]
    fn single_line_comment_test() {
        test_multiple("// Comment.", &[]);
        test_multiple("// let x = 0;", &[]);
        test_multiple(
            "let x; // Comment.",
            &[
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Semicolon,
            ],
        );
    }

    #[test]
    fn multi_line_comment() {
        // On a single line.
        test_multiple("/* Comment. */", &[]);
        test_multiple("/* let x = 0; */", &[]);
        test_multiple(
            "let x; /* Comment. */",
            &[
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Semicolon,
            ],
        );
        test_multiple(
            "/* Comment. */ let x;",
            &[
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Semicolon,
            ],
        );

        // On multiple lines.
        test_multiple("/*\nComment.\n*/", &[]);
        test_multiple("/*\nlet x = 0;\n*/", &[]);
        test_multiple(
            "let x; /*\nComment.\n*/",
            &[
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Semicolon,
            ],
        );
        test_multiple("/* comment * more */", &[]);
        test_multiple("/*\n3*3/9\n*/", &[]);

        // Nested comment.
        test_multiple("/* /* inner */ outer */", &[]);
    }

    #[test]
    fn valid_string() {
        // Basic strings.
        test_single("\"\"", TokenKind::String(String::new()));
        test_single("\"hello\"", TokenKind::String("hello".to_owned()));
        test_single("\"world\"", TokenKind::String("world".to_owned()));
        test_single(
            "\"Hello, World!\"",
            TokenKind::String("Hello, World!".to_owned()),
        );

        // Strings with numbers and symbols.
        test_single("\"123\"", TokenKind::String("123".to_owned()));
        test_single("\"test123\"", TokenKind::String("test123".to_owned()));
        test_single("\"!@#$%^&*()\"", TokenKind::String("!@#$%^&*()".to_owned()));
        test_single(
            "\"spaces   here\"",
            TokenKind::String("spaces   here".to_owned()),
        );

        // Simple escape sequences.
        test_single("\"\\\"\"", TokenKind::String("\"".to_owned()));
        test_single("\"\\\\\"", TokenKind::String("\\".to_owned()));
        test_single("\"\\n\"", TokenKind::String("\n".to_owned()));
        test_single("\"\\r\"", TokenKind::String("\r".to_owned()));
        test_single("\"\\t\"", TokenKind::String("\t".to_owned()));
        test_single("\"\\b\"", TokenKind::String("\u{0008}".to_owned()));
        test_single("\"\\f\"", TokenKind::String("\u{000C}".to_owned()));
        test_single("\"\\0\"", TokenKind::String("\0".to_owned()));

        // Mixed content with escapes.
        test_single(
            "\"Hello\\nWorld\"",
            TokenKind::String("Hello\nWorld".to_owned()),
        );
        test_single(
            "\"Path\\\\to\\\\file\"",
            TokenKind::String("Path\\to\\file".to_owned()),
        );
        test_single(
            "\"Quote: \\\"text\\\"\"",
            TokenKind::String("Quote: \"text\"".to_owned()),
        );
        test_single("\"Tab\\there\"", TokenKind::String("Tab\there".to_owned()));

        // Hex escape sequences.
        test_single("\"\\x41\"", TokenKind::String("A".to_owned())); // 0x41 = 'A'.
        test_single("\"\\x20\"", TokenKind::String(" ".to_owned())); // 0x20 = space.
        test_single("\"\\x00\"", TokenKind::String("\0".to_owned())); // 0x00 = null.
        test_single("\"\\xFF\"", TokenKind::String("\u{FF}".to_owned())); // 0xFF = 255.
        test_single(
            "\"Hello\\x20World\"",
            TokenKind::String("Hello World".to_owned()),
        );

        // Unicode escape sequences (\u).
        test_single("\"\\u0041\"", TokenKind::String("A".to_owned())); // U+0041 = 'A'.
        test_single("\"\\u0020\"", TokenKind::String(" ".to_owned())); // U+0020 = space.
        test_single("\"\\u03B1\"", TokenKind::String("α".to_owned())); // U+03B1 = Greek alpha.
        test_single("\"\\u2603\"", TokenKind::String("☃".to_owned())); // U+2603 = snowman.
        test_single(
            "\"Hello\\u0020World\"",
            TokenKind::String("Hello World".to_owned()),
        );

        // Unicode escape sequences (\U).
        test_single("\"\\U00000041\"", TokenKind::String("A".to_owned())); // U+00000041 = 'A'.
        test_single("\"\\U00000020\"", TokenKind::String(" ".to_owned())); // U+00000020 = space.
        test_single("\"\\U000003B1\"", TokenKind::String("α".to_owned())); // U+000003B1 = Greek alpha.
        test_single("\"\\U00002603\"", TokenKind::String("☃".to_owned())); // U+00002603 = snowman.
        test_single("\"\\U0001F600\"", TokenKind::String("😀".to_owned())); // U+0001F600 = grinning face.

        // Mixed escape types.
        test_single(
            "\"\\x41\\u0042\\U00000043\"",
            TokenKind::String("ABC".to_owned()),
        );
        test_single("\"\\n\\t\\r\"", TokenKind::String("\n\t\r".to_owned()));
        test_single(
            "\"\\\"Hello\\\"\\n\"",
            TokenKind::String("\"Hello\"\n".to_owned()),
        );

        // Edge cases.
        test_single("\"\\x0A\"", TokenKind::String("\n".to_owned())); // 0x0A = newline.
        test_single("\"\\u000A\"", TokenKind::String("\n".to_owned())); // U+000A = newline.
        test_single("\"\\U0000000A\"", TokenKind::String("\n".to_owned())); // U+0000000A = newline.
    }

    #[test]
    fn invalid_string() {
        // Unterminated strings.
        test_error("\"");
        test_error("\"hello");
        test_error("\"hello world");
        test_error("\"unterminated string with escape\\n");
        test_error("\"unterminated with quote\\\"");

        // Invalid escape sequences.
        test_error("\"\\a\""); // Invalid escape character.
        test_error("\"\\z\""); // Invalid escape character.
        test_error("\"\\1\""); // Invalid escape character.
        test_error("\"\\!\""); // Invalid escape character.
        test_error("\"\\@\""); // Invalid escape character.
        test_error("\"\\#\""); // Invalid escape character.

        // Incomplete escape sequences.
        test_error("\"\\\""); // Backslash at end.
        test_error("\"hello\\\""); // Backslash at end.

        // Invalid hex escape sequences.
        test_error("\"\\x\""); // Missing hex digits.
        test_error("\"\\x4\""); // Only one hex digit.
        test_error("\"\\xGG\""); // Invalid hex digits.
        test_error("\"\\x4G\""); // One valid, one invalid hex digit.
        test_error("\"\\xZ1\""); // Invalid hex digits.
        test_error("\"\\x!@\""); // Invalid hex digits.

        // Invalid unicode escape sequences (\u).
        test_error("\"\\u\""); // Missing hex digits.
        test_error("\"\\u4\""); // Only one hex digit.
        test_error("\"\\u41\""); // Only two hex digits.
        test_error("\"\\u414\""); // Only three hex digits.
        test_error("\"\\uGGGG\""); // Invalid hex digits.
        test_error("\"\\u41GG\""); // Mixed valid/invalid hex digits.
        test_error("\"\\u!@#$\""); // Invalid hex digits.

        // Invalid unicode escape sequences (\U).
        test_error("\"\\U\""); // Missing hex digits.
        test_error("\"\\U4\""); // Only one hex digit.
        test_error("\"\\U41\""); // Only two hex digits.
        test_error("\"\\U414\""); // Only three hex digits.
        test_error("\"\\U4141\""); // Only four hex digits.
        test_error("\"\\U41414\""); // Only five hex digits.
        test_error("\"\\U414141\""); // Only six hex digits.
        test_error("\"\\U4141414\""); // Only seven hex digits.
        test_error("\"\\UGGGGGGGG\""); // Invalid hex digits.
        test_error("\"\\U414141GG\""); // Mixed valid/invalid hex digits.
        test_error("\"\\U1234567G\""); // Invalid unicode escape.
        test_error("\"\\U!@#$%^&*\""); // Invalid hex digits.

        // Strings with newlines (not escaped).
        test_error("\"hello\nworld\""); // Actual newline in string.
        test_error("\"line1\nline2\""); // Actual newline in string.
        test_error("\"hello\rworld\""); // Actual carriage return in string.

        // Mixed invalid cases.
        test_error("\"valid start\\x"); // Valid start but incomplete escape.
        test_error("\"valid start\\u123"); // Valid start but incomplete escape.
        test_error("\"valid start\\U1234567"); // Valid start but incomplete escape.
        test_error("\"escape\\x4Gthen more\""); // Invalid hex then more content.
    }

    #[test]
    #[allow(clippy::approx_constant)] // 3.14 is a lexer test literal, not an approximation of pi.
    fn valid_number() {
        let test_single_number = |s: &str, n: f64| {
            test_single(s, TokenKind::Number(n));
        };

        (0..=9).for_each(|n| test_single_number(&n.to_string(), f64::from(n)));

        test_single_number("42", 42.0);
        test_single_number("123", 123.0);
        test_single_number("1000", 1000.0);
        test_single_number("999999", 999_999.0);

        test_single_number("0.0", 0.0);
        test_single_number("1.0", 1.0);
        test_single_number("0.5", 0.5);
        test_single_number("3.14", 3.14);
        test_single_number("123.456", 123.456);
        test_single_number("0.123456789", 0.123_456_789);

        test_single_number("00", 0.0);
        test_single_number("01", 1.0);
        test_single_number("0123", 123.0);
        test_single_number("00.00", 0.0);
        test_single_number("01.23", 1.23);
    }

    #[test]
    fn valid_number_with_range() {
        test_multiple(
            "1..10",
            &[
                TokenKind::Number(1.0),
                TokenKind::Range,
                TokenKind::Number(10.0),
            ],
        );
        test_multiple(
            "1 ..10",
            &[
                TokenKind::Number(1.0),
                TokenKind::Range,
                TokenKind::Number(10.0),
            ],
        );
        test_multiple(
            "1.. 10",
            &[
                TokenKind::Number(1.0),
                TokenKind::Range,
                TokenKind::Number(10.0),
            ],
        );
        test_multiple(
            "1..=10",
            &[
                TokenKind::Number(1.0),
                TokenKind::RangeInclusive,
                TokenKind::Number(10.0),
            ],
        );
        test_multiple(
            "1 ..=10",
            &[
                TokenKind::Number(1.0),
                TokenKind::RangeInclusive,
                TokenKind::Number(10.0),
            ],
        );
        test_multiple(
            "1.. =10",
            &[
                TokenKind::Number(1.0),
                TokenKind::Range,
                TokenKind::Is,
                TokenKind::Number(10.0),
            ],
        );
    }

    #[test]
    fn invalid_number() {
        test_error("0.");
        test_error("1.");
        test_error("9.");
        test_error("42.");
        test_error("123.");
        test_error("1000.");
        test_error("00.");
        test_error("0123.");
    }

    #[test]
    fn valid_identifiers() {
        test_single("x", TokenKind::Identifier("x".to_owned()));
        test_single("ɑ", TokenKind::Identifier("ɑ".to_owned()));
        test_single("ah", TokenKind::Identifier("ah".to_owned()));
        test_single("word", TokenKind::Identifier("word".to_owned()));
        test_single("CamelCase", TokenKind::Identifier("CamelCase".to_owned()));
        test_single("snake_case", TokenKind::Identifier("snake_case".to_owned()));
        test_single("ALLUPPER", TokenKind::Identifier("ALLUPPER".to_owned()));
        test_single("ŮñĭçøƋɇ", TokenKind::Identifier("ŮñĭçøƋɇ".to_owned()));
        test_single(
            "trailing_numbers012",
            TokenKind::Identifier("trailing_numbers012".to_owned()),
        );
        test_single(
            "numbers1n8etw33n",
            TokenKind::Identifier("numbers1n8etw33n".to_owned()),
        );
        test_single(
            "veeeeeeeerylooooooongwooooooord",
            TokenKind::Identifier("veeeeeeeerylooooooongwooooooord".to_owned()),
        );
    }

    #[test]
    fn invalid_identifiers() {
        let test_error_result = |s| {
            let r = Scanner::new(s).scan();

            assert!(r.is_err(), "Expected unsuccessful scan result");
        };

        test_error_result("😜".into()); // Non-alphabetic.

        // Test unexpected characters.
        test_error("@");
        test_error("#");
        test_error("$");
        test_error("^");
        test_error("~");
        test_error("`");
    }

    #[test]
    #[allow(clippy::too_many_lines)] // A sequence of 8 clearly-labeled scenarios; splitting loses nothing but adds ceremony.
    fn valid_sequence() {
        // Test 1: Variable declaration with assignment.
        test_multiple(
            "let x = 42;",
            &[
                TokenKind::Let,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Is,
                TokenKind::Number(42.0),
                TokenKind::Semicolon,
            ],
        );

        // Test 2: Function declaration with parameters and arrow return type.
        test_multiple(
            "fn add(a, b) -> Number { return a + b; }",
            &[
                TokenKind::Function,
                TokenKind::Identifier("add".to_owned()),
                TokenKind::LParen,
                TokenKind::Identifier("a".to_owned()),
                TokenKind::Comma,
                TokenKind::Identifier("b".to_owned()),
                TokenKind::RParen,
                TokenKind::Arrow,
                TokenKind::NumberId,
                TokenKind::LBrace,
                TokenKind::Return,
                TokenKind::Identifier("a".to_owned()),
                TokenKind::Plus,
                TokenKind::Identifier("b".to_owned()),
                TokenKind::Semicolon,
                TokenKind::RBrace,
            ],
        );

        // Test 3: Conditional statement with comparison operators.
        test_multiple(
            "if x > 10 && y < 20 { print \"In range\"; }",
            &[
                TokenKind::If,
                TokenKind::Identifier("x".to_owned()),
                TokenKind::Gt,
                TokenKind::Number(10.0),
                TokenKind::And,
                TokenKind::Identifier("y".to_owned()),
                TokenKind::Lt,
                TokenKind::Number(20.0),
                TokenKind::LBrace,
                TokenKind::Print,
                TokenKind::String("In range".to_owned()),
                TokenKind::Semicolon,
                TokenKind::RBrace,
            ],
        );

        // Test 4: Loop with range operator.
        test_multiple(
            "for i in 1..5 { print i; }",
            &[
                TokenKind::For,
                TokenKind::Identifier("i".to_owned()),
                TokenKind::In,
                TokenKind::Number(1.0),
                TokenKind::Range,
                TokenKind::Number(5.0),
                TokenKind::LBrace,
                TokenKind::Print,
                TokenKind::Identifier("i".to_owned()),
                TokenKind::Semicolon,
                TokenKind::RBrace,
            ],
        );

        // Test 5: Node and Group keywords with properties.
        test_multiple(
            "node Circle { radius = 5.0; } group Shapes { circles = [Circle]; }",
            &[
                TokenKind::Node,
                TokenKind::Identifier("Circle".to_owned()),
                TokenKind::LBrace,
                TokenKind::Identifier("radius".to_owned()),
                TokenKind::Is,
                TokenKind::Number(5.0),
                TokenKind::Semicolon,
                TokenKind::RBrace,
                TokenKind::Group,
                TokenKind::Identifier("Shapes".to_owned()),
                TokenKind::LBrace,
                TokenKind::Identifier("circles".to_owned()),
                TokenKind::Is,
                TokenKind::LBracket,
                TokenKind::Identifier("Circle".to_owned()),
                TokenKind::RBracket,
                TokenKind::Semicolon,
                TokenKind::RBrace,
            ],
        );

        // Test 6: Boolean operations, equality and string concatenation.
        test_multiple(
            "let valid = true && !false; let msg = \"Status: \" + (valid == true);",
            &[
                TokenKind::Let,
                TokenKind::Identifier("valid".to_owned()),
                TokenKind::Is,
                TokenKind::True,
                TokenKind::And,
                TokenKind::Bang,
                TokenKind::False,
                TokenKind::Semicolon,
                TokenKind::Let,
                TokenKind::Identifier("msg".to_owned()),
                TokenKind::Is,
                TokenKind::String("Status: ".to_owned()),
                TokenKind::Plus,
                TokenKind::LParen,
                TokenKind::Identifier("valid".to_owned()),
                TokenKind::Eq,
                TokenKind::True,
                TokenKind::RParen,
                TokenKind::Semicolon,
            ],
        );

        // Test 7: Mutable variable with arithmetic operations.
        test_multiple(
            "let mut total = 100; total = total - 50; total = total * 2; total = total / 4;",
            &[
                TokenKind::Let,
                TokenKind::Mut,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Is,
                TokenKind::Number(100.0),
                TokenKind::Semicolon,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Is,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Minus,
                TokenKind::Number(50.0),
                TokenKind::Semicolon,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Is,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Star,
                TokenKind::Number(2.0),
                TokenKind::Semicolon,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Is,
                TokenKind::Identifier("total".to_owned()),
                TokenKind::Slash,
                TokenKind::Number(4.0),
                TokenKind::Semicolon,
            ],
        );

        // Test 8: Complex expression with nested parentheses and all arithmetic operators.
        test_multiple(
            "let result = (10 + 5) * ((20 - 8) / 4) % 3;",
            &[
                TokenKind::Let,
                TokenKind::Identifier("result".to_owned()),
                TokenKind::Is,
                TokenKind::LParen,
                TokenKind::Number(10.0),
                TokenKind::Plus,
                TokenKind::Number(5.0),
                TokenKind::RParen,
                TokenKind::Star,
                TokenKind::LParen,
                TokenKind::LParen,
                TokenKind::Number(20.0),
                TokenKind::Minus,
                TokenKind::Number(8.0),
                TokenKind::RParen,
                TokenKind::Slash,
                TokenKind::Number(4.0),
                TokenKind::RParen,
                TokenKind::Percent,
                TokenKind::Number(3.0),
                TokenKind::Semicolon,
            ],
        );
    }

    #[test]
    fn invalid_sequence() {
        // Test 1: Unterminated string literal.
        test_error("let message = \"Hello, world");

        // Test 2: Invalid escape sequence in string.
        test_error("let message = \"Hello\\z world\"");

        // Test 3: Malformed number with trailing dot.
        test_error("let value = 42.");

        // Test 4: Unexpected character in the middle of valid code.
        test_error("let x = @10");

        // Test 5: Invalid character that's not allowed anywhere.
        test_error("let name = $variable");

        // Test 6: Invalid hex digits in string escape sequence.
        test_error("let invalid = \"\\xZG\";");

        // Test 7: Incomplete Unicode escape sequence.
        test_error("let invalid = \"\\u123\";"); // Should have 4 hex digits.

        // Test 8: Mix of valid tokens with an invalid character.
        test_error("fn main() { let x = 1 + 2 # 3; }");
    }
}
