// rasm-core/src/parser/pass1.rs
//
//! # Первый проход парсера (Pass 1) — сбор таблицы меток.
//!
//! ## Зачем нужен первый проход?
//!
//! Ассемблер позволяет ссылаться на метку **до** её объявления — это называется
//! forward reference. Например:
//!
//! ```x86asm
//! jmp .end        ; <- метка ещё не объявлена!
//! mov rax, 1
//!
//! .end:           ; <- вот она
//!     ret
//! ```
//!
//! Когда второй проход встретит `jmp .end`, он должен знать, какой порядковый
//! номер инструкции соответствует `.end`. Именно эту задачу решает Pass 1:
//! он проходит по токенам **один раз**, никаких AST-узлов не создаёт,
//! и просто строит [`HashMap<StrId, usize>`] — таблицу меток.
//!
//! ## Как считается "адрес"?
//!
//! На этапе парсинга у нас ещё нет байтовых адресов (они появятся только
//! в codegen, когда мы будем кодировать каждую инструкцию в байты).
//! Поэтому здесь адрес метки — это **порядковый номер инструкции**
//! (instruction index), то есть позиция в будущем векторе `AST::program`.
//! Codegen потом пересчитает эти индексы в реальные байтовые смещения.
use crate::{
    lexer::token::{Token, TokenKind},
    str_pool::{StrId, StrPool},
};
use std::collections::HashMap;

/// Таблица меток: имя метки -> порядковый номер инструкции (instruction index).
pub type LabelTable = HashMap<StrId, usize>;

/// Выполняет первый проход: сканирует токены и собирает таблицу меток.
///
/// Логика очень простая:
/// 1. Счётчик `instruction_index` увеличивается каждый раз, когда мы видим
///    начало **инструкции** (токен `Mnemonic`) или **директивы** (токен [`TokenKind::Ident`]
///    с именем директивы, начинающимся на `.`).
/// 2. Когда мы встречаем паттерн `Ident` + `Colon` — это объявление метки.
///    Запоминаем: `label_name -> текущий instruction_index`.
///
/// Директивы не меняют `instruction_index`, они идут отдельным "слоем",
/// но для простоты мы их тоже считаем — это не критично для корректности
/// разрешения переходов в `.text` секции.
pub fn collect_labels(tokens: &[Token], _: &mut StrPool) -> LabelTable {
    let mut table = LabelTable::new();
    let mut instruction_index: usize = 0;
    let mut i = 0;

    while i < tokens.len() {
        let tok = &tokens[i];

        match tok.kind {
            // Паттерн: Ident ':' — это объявление метки
            TokenKind::Ident(name_id) => {
                if i + 1 < tokens.len()
                    && let TokenKind::Colon = tokens[i + 1].kind
                {
                    // Нашли метку. Привязываем её к текущему instruction_index.
                    table.insert(name_id, instruction_index);
                    // Пропускаем сам Ident и `:` (2 токена)
                    i += 2;
                    continue;
                }
                // Просто идентификатор (например, имя в операнде) — пропускаем
                i += 1;
            }

            // Мнемоника — начало инструкции. Увеличиваем счётчик.
            TokenKind::Mnemonic(_) => {
                instruction_index += 1;
                // Пропускаем до конца строки, чтобы не считать операнды
                i = skip_to_newline(tokens, i + 1);
            }

            TokenKind::Dot => {
                i = skip_to_newline(tokens, i + 1);
            }

            // Переводы строк и конец файла — пропускаем
            TokenKind::Newline | TokenKind::End => {
                i += 1;
            }

            // Всё остальное пропускаем
            _ => {
                i += 1;
            }
        }
    }

    table
}

/// Возвращает индекс токена, следующего за ближайшим [`TokenKind::Newline`] или [`TokenKind::End`].
fn skip_to_newline(tokens: &[Token], from: usize) -> usize {
    let mut i = from;
    while i < tokens.len() {
        match tokens[i].kind {
            TokenKind::Newline | TokenKind::End => return i + 1,
            _ => i += 1,
        }
    }
    i
}
