//! # Лексер
//!
//! В этом модуле реализован лексер.
pub mod err;
pub mod token;

use crate::{
    lexer::{
        err::{LexerError, LexerErrorKind},
        token::{Span, Token, TokenKind},
    },
    src::SourceCode,
    str_pool::StrPool,
};

/// # Лексер (Менеджер лексического анализа)
///
/// `Lexer` отвечает за первый этап компиляции ассемблера **RASM** — превращение плоского
/// исходного текста [`SourceCode`] в поток строго типизированных токенов [`Token`].
///
/// ## Архитектура и принципы работы
///
/// Лексер спроектирован с упором на максимальную производительность, отсутствие лишних
/// аллокаций в куче (Zero-allocation в основном цикле) и дружественность к кэшу процессора.
///
/// 1. **Интернирование строк**: Вместо того чтобы создавать объект [`String`] для каждого
///    встреченного идентификатора, регистра или мнемоники, лексер использует единый мутабельный
///    пул строк [`StrPool`]. Все текстовые данные превращаются в легковесные числовые `StrId`.
/// 2. **Побайтовое сканирование**: Поскольку синтаксис x86_64 ассемблера состоит исключительно
///    из символов ASCII, лексер обходит массив байт напрямую с помощью целочисленного индекса `cursor`.
///    Это исключает накладные расходы на валидацию UTF-8 во время токенизации.
/// 3. **Умное восстановление после ошибок**: Лексер не прекращает работу при первой же синтаксической
///    ошибке. Он фиксирует её в векторе `errors`, пропускает некорректную последовательность символов
///    и продолжает разбор кода дальше, позволяя вывести пользователю полный отчет обо всех проблемах за один запуск.
pub struct Lexer<'a> {
    /// Мутабельная ссылка на глобальный пул строк компилятора.
    /// Используется для сохранения и получения уникальных идентификаторов [`crate::str_pool::StrId`]
    /// пользовательских меток, названий функций, регистров и инструкций.
    pub str_pool: &'a mut StrPool,

    /// Результат работы лексера — линейный вектор успешно разобранных токенов.
    /// Рекомендуется инициализировать с заранее выделенной емкостью ([`Vec::with_capacity`])
    /// на основе размера исходного кода, чтобы избежать частых реаллокаций.
    pub tokens: Vec<Token>,

    /// Список лексических ошибок, обнаруженных в процессе анализа (например, незавершенные литералы,
    /// неизвестные спецсимволы). Если вектор пуст — исходный код синтаксически валиден на уровне символов.
    pub errors: Vec<LexerError>,

    /// Владеющий объект исходного кода, содержащий сырой текст программы и
    /// кэш позиций переноса строк для быстрой генерации координат ошибок.
    source_code: SourceCode,

    /// Текущий указатель считывания (индекс байта в строке `source_code.source`).
    /// Указывает на позицию символа, который будет обработан при следующем вызове `next_char()`.
    /// Гарантированно находится в диапазоне `0..=source_code.source.len()`.
    cursor: usize,
}

impl<'a> Lexer<'a> {
    pub fn new(str_pool: &'a mut StrPool, source_code: SourceCode) -> Self {
        // Оптимизируем изначальный размер вектора токенов,
        // чтобы избежать частых аллокаций в процессе лексического анализа.
        let estimated_tokens = source_code.source.len() / 5;

        Self {
            str_pool,
            tokens: Vec::with_capacity(estimated_tokens),
            errors: Vec::new(),
            source_code,
            cursor: 0,
        }
    }

    /// Запускает полный лексический анализ исходного кода.
    /// Заполняет вектор `self.tokens`.
    pub fn tokenize(&mut self) {
        while let Some(token) = self.next_token() {
            let is_end = token.kind == TokenKind::End;
            self.tokens.push(token);
            if is_end {
                break;
            }
        }
    }

    /// Основной метод лексера, который находит и возвращает следующий валидный токен.
    /// Возвращает `None`, если мы дошли до конца файла (End / EOF).
    ///
    /// Реализован как явный цикл (не рекурсия), чтобы не переполнять стек
    /// на файлах с длинными комментариями или множеством неизвестных символов
    /// (например, кириллические комментарии — каждый байт попадал бы в ветку `_`
    /// и рекурсивно звал `next_token`, что приводило к stack overflow).
    pub fn next_token(&mut self) -> Option<Token> {
        loop {
            self.skip_whitespace();

            let start_pos = self.current_pos();

            // 1. Проверяем конец файла
            let ch = match self.next_char() {
                Some(c) => c,
                None => return Some(Token::new(TokenKind::End, Span::new(start_pos, start_pos))),
            };

            // 2. Обработка одиночных спецсимволов и операторов
            let kind = match ch {
                ',' => TokenKind::Comma,
                ':' => TokenKind::Colon,
                '.' => TokenKind::Dot,
                '[' => TokenKind::OpeningSquareBracket,
                ']' => TokenKind::EndingSquareBracket,
                '+' => TokenKind::Plus,
                '-' => TokenKind::Minus,
                '*' => TokenKind::Asterisk,
                '\n' => TokenKind::Newline,

                '"' => return Some(self.read_string_literal(start_pos)),

                // 3. Комментарий — пропускаем до конца строки и идём на следующую итерацию
                ';' => {
                    self.skip_comment();
                    continue;
                }

                // 4. Числа
                c if c.is_ascii_digit() => return Some(self.read_number(start_pos, c)),

                // 5. Идентификаторы, регистры, мнемоники, типы
                c if c.is_ascii_alphabetic() || c == '_' => {
                    return Some(self.read_identifier(start_pos, c));
                }

                // 6. Неизвестный символ (включая не-ASCII байты кириллицы и т.д.) —
                //    фиксируем ошибку и идём на следующую итерацию цикла.
                //    Важно: не вызываем next_token рекурсивно — это и была причина
                //    stack overflow на файлах с кириллическими комментариями.
                _ => {
                    // Пишем ошибку только для печатаемых ASCII символов —
                    // не-ASCII байты это просто части многобайтовых символов UTF-8,
                    // засорять ошибками вывод не нужно.
                    if ch.is_ascii() {
                        let err_span = Span::new(start_pos, self.current_pos());
                        self.errors.push(LexerError::new(
                            LexerErrorKind::UnexpectedChar(ch),
                            err_span,
                        ));
                    }
                    continue;
                }
            };

            let end_pos = self.current_pos();
            return Some(Token::new(kind, Span::new(start_pos, end_pos)));
        }
    }

    /// Пропускает пробелы и табуляцию, но останавливается перед переносом строки
    fn skip_whitespace(&mut self) {
        while let Some(ch) = self.peek_char() {
            if ch == ' ' || ch == '\t' || ch == '\r' {
                self.next_char();
            } else {
                break;
            }
        }
    }

    /// Пропускает все символы внутри комментария до конца строки или файла
    fn skip_comment(&mut self) {
        while let Some(ch) = self.peek_char() {
            if ch == '\n' {
                break; // Перенос строки обработает основной цикл next_token
            }
            self.next_char();
        }
    }

    /// Считывает один Unicode-символ начиная с текущей позиции курсора,
    /// корректно обрабатывая многобайтовые UTF-8 последовательности.
    /// В отличие от `next_char`, предназначенного для ASCII-структуры токенов
    /// (мнемоники, числа, операторы), этот метод используется только внутри
    /// строковых литералов, где пользовательский текст может быть произвольным
    /// Unicode (например, кириллица в `.ascii "Привет"`).
    #[inline]
    fn next_char_utf8(&mut self) -> Option<char> {
        let rest = &self.source_code.source[self.cursor..];
        let ch = rest.chars().next()?;
        self.cursor += ch.len_utf8();
        Some(ch)
    }

    /// Считывает числовой литерал (целое или дробное)
    fn read_number(&mut self, start_pos: u32, first_char: char) -> Token {
        let mut raw_str = String::new();
        raw_str.push(first_char);

        // Поддерживаем hex (0x1A), если надо, или просто собираем цифры и точку
        while let Some(ch) = self.peek_char() {
            if ch.is_ascii_alphanumeric() || ch == '.' {
                raw_str.push(ch);
                self.next_char();
            } else {
                break;
            }
        }

        let end_pos = self.current_pos();
        let span = Span::new(start_pos, end_pos);

        // Проверяем, дробное это число или целое
        if raw_str.contains('.') {
            if let Ok(val) = raw_str.parse::<f64>() {
                Token::new(TokenKind::Float(val), span)
            } else {
                self.errors
                    .push(LexerError::new(LexerErrorKind::InvalidFloat(raw_str), span));
                Token::new(TokenKind::Float(0.0), span)
            }
        } else {
            // Пробуем распарсить как hex или dec
            let val: Result<i64, _> = match raw_str.to_lowercase() {
                v if v.starts_with("0x") => {
                    u64::from_str_radix(&raw_str[2..], 16).map(|v| v as i64)
                }
                v if v.starts_with("0b") => u64::from_str_radix(&raw_str[2..], 2).map(|v| v as i64),
                _ => raw_str.parse::<i64>(),
            };

            match val {
                Ok(v) => Token::new(TokenKind::Integer(v), span),
                Err(_) => {
                    self.errors.push(LexerError::new(
                        LexerErrorKind::InvalidInteger(raw_str),
                        span,
                    ));
                    Token::new(TokenKind::Integer(0), span)
                }
            }
        }
    }

    /// Считывает слово и определяет, является ли оно мнемоникой, регистром, типом данных или просто Ident
    fn read_identifier(&mut self, start_pos: u32, first_char: char) -> Token {
        let mut word = String::new();
        word.push(first_char);

        while let Some(ch) = self.peek_char() {
            if ch.is_ascii_alphanumeric() || ch == '_' || ch == '.' {
                word.push(ch);
                self.next_char();
            } else {
                break;
            }
        }

        let end_pos = self.current_pos();
        let span = Span::new(start_pos, end_pos);

        // Используем FromStr твоих перечислений из модуля ISA для мгновенного матчинга!
        use std::str::FromStr;

        // 1. Проверяем, не мнемоника ли это (mov, add...)
        if let Ok(mnemonic) = crate::isa::MnemonicKind::from_str(&word) {
            return Token::new(TokenKind::Mnemonic(mnemonic), span);
        }

        // 2. Проверяем, не регистр ли это (rax, xmm0...)
        if let Ok(register) = crate::isa::Registers::from_str(&word) {
            return Token::new(TokenKind::Register(register), span);
        }

        // 3. Проверяем, не тип ли данных (qword, dword...)
        if let Ok(data_type) = crate::isa::DataType::from_str(&word) {
            return Token::new(TokenKind::Type(data_type), span);
        }

        // Игнорируем синтаксический шум вроде слова "ptr" в "qword ptr",
        // возвращая его как обычный Ident (парсер его просто пропустит)

        // 4. Если ничего не подошло — это пользовательская метка или название функции
        let str_id = self.str_pool.intern(&word);
        Token::new(TokenKind::Ident(str_id), span)
    }

    /// Считывает строковый литерал в кавычках: `"hello\nworld"`
    /// Вызывается после того, как открывающая `"` уже потреблена.
    ///
    /// Поддерживаемые escape-последовательности:
    /// `\n`, `\t`, `\r`, `\\`, `\"`, `\0`
    fn read_string_literal(&mut self, start_pos: u32) -> Token {
        let mut result = String::new();

        loop {
            match self.next_char() {
                // Закрывающая кавычка — конец строки
                Some('"') => break,

                // Конец файла или перенос строки без закрывающей кавычки — ошибка
                None | Some('\n') => {
                    let span = Span::new(start_pos, self.current_pos());
                    self.errors
                        .push(LexerError::new(LexerErrorKind::UnterminatedString, span));
                    // Возвращаем то, что успели накопить, чтобы не терять токен
                    let id = self.str_pool.intern(&result);
                    return Token::new(TokenKind::StringLiteral(id), span);
                }

                // Escape-последовательность
                Some('\\') => {
                    match self.next_char_utf8() {
                        Some('n') => result.push('\n'),
                        Some('t') => result.push('\t'),
                        Some('r') => result.push('\r'),
                        Some('0') => result.push('\0'),
                        Some('\\') => result.push('\\'),
                        Some('"') => result.push('"'),
                        Some(c) => {
                            // Неизвестная escape — записываем как есть (\X -> \X)
                            result.push('\\');
                            result.push(c);
                        }
                        None => {
                            let span = Span::new(start_pos, self.current_pos());
                            self.errors
                                .push(LexerError::new(LexerErrorKind::UnterminatedString, span));
                            let id = self.str_pool.intern(&result);
                            return Token::new(TokenKind::StringLiteral(id), span);
                        }
                    }
                }

                // Обычный символ — просто добавляем
                Some(c) => result.push(c),
            }
        }

        let span = Span::new(start_pos, self.current_pos());
        let id = self.str_pool.intern(&result);
        Token::new(TokenKind::StringLiteral(id), span)
    }

    /// Возвращает текущий байт кода и сдвигает указатель вперед.
    /// Если код закончился, возвращает `None`.
    #[inline(always)]
    fn next_char(&mut self) -> Option<char> {
        if self.cursor >= self.source_code.source.len() {
            return None;
        }

        // Быстрое извлечение байта (работает со скоростью прямого обращения к памяти)
        let byte = self.source_code.source.as_bytes()[self.cursor];
        self.cursor += 1;

        Some(byte as char)
    }

    /// Позволяет "подсмотреть" текущий символ без сдвига курсора.
    /// Очень полезно, например, чтобы понять, идет ли за числом точка или буква.
    #[inline(always)]
    fn peek_char(&self) -> Option<char> {
        if self.cursor >= self.source_code.source.len() {
            None
        } else {
            Some(self.source_code.source.as_bytes()[self.cursor] as char)
        }
    }

    /// Возвращает текущую позицию курсора (нужно для формирования Span токенов)
    #[inline(always)]
    fn current_pos(&self) -> u32 {
        self.cursor as u32
    }
}
