use crate::span::Span;
use crate::token::{Operator, Token, TokenKind};

/// A line-oriented lexer for CalcPad expressions.
pub struct Lexer<'a> {
    input: &'a str,
    bytes: &'a [u8],
    pos: usize,
    pending: Option<Token>,
}

impl<'a> Lexer<'a> {
    pub fn new(input: &'a str) -> Self {
        Self {
            input,
            bytes: input.as_bytes(),
            pos: 0,
            pending: None,
        }
    }

    /// Tokenize the entire input line into a token stream, terminated with Eof.
    pub fn tokenize(&mut self) -> Vec<Token> {
        if self.input.trim().is_empty() {
            return vec![Token::new(TokenKind::Eof, Span::new(0, 0))];
        }

        // Full-line comment check
        let trimmed = self.input.trim_start();
        let trimmed_start = self.input.len() - trimmed.len();
        if trimmed.starts_with("//") {
            let comment_text = self.input[trimmed_start + 2..].to_string();
            return vec![
                Token::new(
                    TokenKind::Comment(comment_text),
                    Span::new(trimmed_start, self.input.len()),
                ),
                Token::new(TokenKind::Eof, Span::new(self.input.len(), self.input.len())),
            ];
        }

        let mut tokens = Vec::new();
        loop {
            if let Some(tok) = self.pending.take() {
                tokens.push(tok);
                continue;
            }
            if self.pos >= self.bytes.len() {
                break;
            }
            self.skip_whitespace();
            if self.pos >= self.bytes.len() {
                break;
            }
            if let Some(tok) = self.scan_token() {
                tokens.push(tok);
            }
        }

        // If no calculable tokens, return as text
        if tokens.is_empty() && !self.input.trim().is_empty() {
            return vec![
                Token::new(
                    TokenKind::Text(self.input.to_string()),
                    Span::new(0, self.input.len()),
                ),
                Token::new(TokenKind::Eof, Span::new(self.input.len(), self.input.len())),
            ];
        }

        let has_assign = tokens.iter().any(|t| matches!(t.kind, TokenKind::Assign));
        let has_calculable = tokens.iter().any(|t| {
            matches!(
                t.kind,
                TokenKind::Number(_)
                    | TokenKind::Op(_)
                    | TokenKind::Assign
                    | TokenKind::Percent(_)
                    | TokenKind::CurrencySymbol(_)
                    | TokenKind::Unit(_)
                    | TokenKind::LParen
                    | TokenKind::RParen
                    | TokenKind::LineRef(_)
                    | TokenKind::PrevRef
            )
        });
        // A single identifier token (potential variable reference) is also calculable
        let is_single_identifier = !has_calculable
            && tokens.len() == 1
            && matches!(tokens[0].kind, TokenKind::Identifier(_));
        // An identifier with assignment is also calculable
        let has_identifier_with_assign = has_assign
            && tokens.iter().any(|t| matches!(t.kind, TokenKind::Identifier(_)));
        if !has_calculable && !is_single_identifier && !has_identifier_with_assign {
            return vec![
                Token::new(
                    TokenKind::Text(self.input.to_string()),
                    Span::new(0, self.input.len()),
                ),
                Token::new(TokenKind::Eof, Span::new(self.input.len(), self.input.len())),
            ];
        }

        let end = self.input.len();
        tokens.push(Token::new(TokenKind::Eof, Span::new(end, end)));
        tokens
    }

    fn skip_whitespace(&mut self) {
        while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
            self.pos += 1;
        }
    }

    fn peek(&self) -> Option<u8> {
        self.bytes.get(self.pos).copied()
    }

    fn peek_ahead(&self, n: usize) -> Option<u8> {
        self.bytes.get(self.pos + n).copied()
    }

    fn advance(&mut self) -> Option<u8> {
        if self.pos < self.bytes.len() {
            let b = self.bytes[self.pos];
            self.pos += 1;
            Some(b)
        } else {
            None
        }
    }

    fn matches_word(&self, word: &str) -> bool {
        let remaining = &self.input[self.pos..];
        if remaining.len() < word.len() {
            return false;
        }
        if !remaining[..word.len()].eq_ignore_ascii_case(word) {
            return false;
        }
        if remaining.len() == word.len() {
            return true;
        }
        let next = remaining.as_bytes()[word.len()];
        !next.is_ascii_alphanumeric() && next != b'_'
    }

    fn scan_token(&mut self) -> Option<Token> {
        let b = self.peek()?;

        // Comment: // ...
        if b == b'/' && self.peek_ahead(1) == Some(b'/') {
            return Some(self.scan_comment());
        }

        // Hash line reference: #N
        if b == b'#' {
            if let Some(tok) = self.try_scan_hash_line_ref() {
                return Some(tok);
            }
        }

        // Currency symbols
        if let Some(tok) = self.try_scan_currency() {
            return Some(tok);
        }

        // Numbers
        if b.is_ascii_digit()
            || (b == b'.' && self.peek_ahead(1).is_some_and(|c| c.is_ascii_digit()))
        {
            return Some(self.scan_number());
        }

        // Two-character comparison operators
        if b == b'>' && self.peek_ahead(1) == Some(b'=') {
            let start = self.pos;
            self.pos += 2;
            return Some(Token::new(TokenKind::GreaterEq, Span::new(start, self.pos)));
        }
        if b == b'<' && self.peek_ahead(1) == Some(b'=') {
            let start = self.pos;
            self.pos += 2;
            return Some(Token::new(TokenKind::LessEq, Span::new(start, self.pos)));
        }
        if b == b'=' && self.peek_ahead(1) == Some(b'=') {
            let start = self.pos;
            self.pos += 2;
            return Some(Token::new(TokenKind::Equal, Span::new(start, self.pos)));
        }
        if b == b'!' && self.peek_ahead(1) == Some(b'=') {
            let start = self.pos;
            self.pos += 2;
            return Some(Token::new(TokenKind::NotEqual, Span::new(start, self.pos)));
        }

        // Single-character operators and punctuation
        match b {
            b'+' => return Some(self.single_char_token(TokenKind::Op(Operator::Add))),
            b'-' => return Some(self.single_char_token(TokenKind::Op(Operator::Subtract))),
            b'*' => return Some(self.single_char_token(TokenKind::Op(Operator::Multiply))),
            b'/' => return Some(self.single_char_token(TokenKind::Op(Operator::Divide))),
            b'^' => return Some(self.single_char_token(TokenKind::Op(Operator::Power))),
            b'(' => return Some(self.single_char_token(TokenKind::LParen)),
            b')' => return Some(self.single_char_token(TokenKind::RParen)),
            b'>' => return Some(self.single_char_token(TokenKind::Greater)),
            b'<' => return Some(self.single_char_token(TokenKind::Less)),
            b'=' => return Some(self.single_char_token(TokenKind::Assign)),
            b'%' => return Some(self.single_char_token(TokenKind::Op(Operator::Modulo))),
            _ => {}
        }

        // Alphabetic — could be keyword, unit, or identifier
        if b.is_ascii_alphabetic() || b == b'_' {
            return Some(self.scan_word());
        }

        // Unknown character — skip
        self.advance();
        None
    }

    fn single_char_token(&mut self, kind: TokenKind) -> Token {
        let start = self.pos;
        self.advance();
        Token::new(kind, Span::new(start, self.pos))
    }

    fn scan_comment(&mut self) -> Token {
        let start = self.pos;
        self.pos += 2;
        let text = self.input[self.pos..].to_string();
        self.pos = self.bytes.len();
        Token::new(TokenKind::Comment(text), Span::new(start, self.pos))
    }

    fn try_scan_currency(&mut self) -> Option<Token> {
        let start = self.pos;
        let remaining = &self.input[self.pos..];

        if remaining.starts_with("R$") {
            self.pos += 2;
            return Some(Token::new(
                TokenKind::CurrencySymbol("R$".to_string()),
                Span::new(start, self.pos),
            ));
        }
        if remaining.starts_with('$') {
            self.pos += 1;
            return Some(Token::new(
                TokenKind::CurrencySymbol("$".to_string()),
                Span::new(start, self.pos),
            ));
        }
        for sym in &["€", "£", "¥"] {
            if remaining.starts_with(sym) {
                self.pos += sym.len();
                return Some(Token::new(
                    TokenKind::CurrencySymbol(sym.to_string()),
                    Span::new(start, self.pos),
                ));
            }
        }
        None
    }

    fn scan_number(&mut self) -> Token {
        let start = self.pos;

        self.consume_digits();

        // Decimal point
        if self.peek() == Some(b'.')
            && self.peek_ahead(1).is_some_and(|c| c.is_ascii_digit())
        {
            self.advance();
            self.consume_digits();
        }

        // Scientific notation
        if let Some(e) = self.peek() {
            if e == b'e' || e == b'E' {
                let next = self.peek_ahead(1);
                let has_digits = match next {
                    Some(b'+') | Some(b'-') => {
                        self.peek_ahead(2).is_some_and(|c| c.is_ascii_digit())
                    }
                    Some(c) => c.is_ascii_digit(),
                    None => false,
                };
                if has_digits {
                    self.advance();
                    if let Some(b'+') | Some(b'-') = self.peek() {
                        self.advance();
                    }
                    self.consume_digits();
                }
            }
        }

        let number_end = self.pos;
        let raw_number: f64 = self.input[start..number_end].parse().unwrap_or(0.0);

        // Check for percent suffix
        if self.peek() == Some(b'%') {
            self.advance();
            return Token::new(TokenKind::Percent(raw_number), Span::new(start, self.pos));
        }

        // SI scale suffixes
        if let Some(suffix) = self.peek() {
            let scale = match suffix {
                b'k' if !self.is_unit_suffix_start() => Some(1_000.0),
                b'M' if !self.peek_ahead(1).is_some_and(|c| c.is_ascii_alphabetic()) => {
                    Some(1_000_000.0)
                }
                b'B' if !self.peek_ahead(1).is_some_and(|c| c.is_ascii_alphabetic()) => {
                    Some(1_000_000_000.0)
                }
                b'T' if !self.peek_ahead(1).is_some_and(|c| c.is_ascii_alphabetic()) => {
                    Some(1_000_000_000_000.0)
                }
                _ => None,
            };
            if let Some(multiplier) = scale {
                self.advance();
                return Token::new(
                    TokenKind::Number(raw_number * multiplier),
                    Span::new(start, self.pos),
                );
            }
        }

        // Unit suffix directly after number
        if let Some(b) = self.peek() {
            if b.is_ascii_alphabetic() {
                let unit_start = self.pos;
                self.consume_alpha();
                let unit_str = self.input[unit_start..self.pos].to_string();
                self.pending = Some(Token::new(
                    TokenKind::Unit(unit_str),
                    Span::new(unit_start, self.pos),
                ));
                return Token::new(
                    TokenKind::Number(raw_number),
                    Span::new(start, number_end),
                );
            }
        }

        Token::new(TokenKind::Number(raw_number), Span::new(start, number_end))
    }

    fn is_unit_suffix_start(&self) -> bool {
        self.peek_ahead(1).is_some_and(|c| c.is_ascii_alphabetic())
    }

    fn consume_digits(&mut self) {
        while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_digit() {
            self.pos += 1;
        }
    }

    fn consume_alpha(&mut self) {
        while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_alphabetic() {
            self.pos += 1;
        }
    }

    fn try_scan_hash_line_ref(&mut self) -> Option<Token> {
        // Check if the character after # is a digit
        if let Some(next) = self.peek_ahead(1) {
            if next.is_ascii_digit() {
                let start = self.pos;
                self.pos += 1; // skip '#'
                let num_start = self.pos;
                self.consume_digits();
                let num_str = &self.input[num_start..self.pos];
                if let Ok(line_num) = num_str.parse::<usize>() {
                    return Some(Token::new(
                        TokenKind::LineRef(line_num),
                        Span::new(start, self.pos),
                    ));
                }
            }
        }
        None
    }

    fn scan_word(&mut self) -> Token {
        // "divided by" two-word operator
        if self.matches_word("divided") {
            let start = self.pos;
            self.pos += "divided".len();
            self.skip_whitespace();
            if self.matches_word("by") {
                self.pos += "by".len();
                return Token::new(
                    TokenKind::Op(Operator::Divide),
                    Span::new(start, self.pos),
                );
            }
            self.pos = start + "divided".len();
            return Token::new(
                TokenKind::Identifier("divided".to_string()),
                Span::new(start, self.pos),
            );
        }

        // Natural language operators
        let nl_ops: &[(&str, Operator)] = &[
            ("plus", Operator::Add),
            ("minus", Operator::Subtract),
            ("times", Operator::Multiply),
        ];
        for &(word, ref op) in nl_ops {
            if self.matches_word(word) {
                let start = self.pos;
                self.pos += word.len();
                return Token::new(TokenKind::Op(*op), Span::new(start, self.pos));
            }
        }

        // prev / ans keywords (previous line reference)
        if self.matches_word("prev") {
            let start = self.pos;
            self.pos += 4;
            return Token::new(TokenKind::PrevRef, Span::new(start, self.pos));
        }
        if self.matches_word("ans") {
            let start = self.pos;
            self.pos += 3;
            return Token::new(TokenKind::PrevRef, Span::new(start, self.pos));
        }

        // lineN syntax for line references (e.g., line1, line42)
        // Can't use matches_word since "line1" has a digit after "line"
        {
            let remaining = &self.input[self.pos..];
            if remaining.len() >= 5
                && remaining[..4].eq_ignore_ascii_case("line")
                && remaining.as_bytes()[4].is_ascii_digit()
            {
                let start = self.pos;
                self.pos += 4; // skip "line"
                let num_start = self.pos;
                self.consume_digits();
                let num_str = &self.input[num_start..self.pos];
                if let Ok(line_num) = num_str.parse::<usize>() {
                    return Token::new(
                        TokenKind::LineRef(line_num),
                        Span::new(start, self.pos),
                    );
                }
                // Failed to parse — revert to identifier
                self.pos = start;
                while self.pos < self.bytes.len()
                    && (self.bytes[self.pos].is_ascii_alphanumeric() || self.bytes[self.pos] == b'_')
                {
                    self.pos += 1;
                }
                let word = self.input[start..self.pos].to_string();
                return Token::new(TokenKind::Identifier(word), Span::new(start, self.pos));
            }
        }

        // The keyword `in` (for conversions)
        if self.matches_word("in") {
            let start = self.pos;
            self.pos += 2;
            return Token::new(TokenKind::In, Span::new(start, self.pos));
        }

        // Other keywords
        let keywords = ["to", "as", "of", "discount", "off", "euro", "usd", "gbp"];
        for kw in &keywords {
            if self.matches_word(kw) {
                let start = self.pos;
                self.pos += kw.len();
                return Token::new(
                    TokenKind::Keyword(kw.to_string()),
                    Span::new(start, self.pos),
                );
            }
        }

        // Generic identifier
        let start = self.pos;
        while self.pos < self.bytes.len()
            && (self.bytes[self.pos].is_ascii_alphanumeric() || self.bytes[self.pos] == b'_')
        {
            self.pos += 1;
        }
        let word = self.input[start..self.pos].to_string();
        Token::new(TokenKind::Identifier(word), Span::new(start, self.pos))
    }
}

/// Convenience function to tokenize an input line.
pub fn tokenize(input: &str) -> Vec<Token> {
    Lexer::new(input).tokenize()
}
