// rasm-core/src/isa/directive.rs
//
//! Директивы.
use crate::{
    isa::err::{ISAError, ISAErrorKind},
    str_pool::StrId,
};
use std::str::FromStr;

/// # Директива ассемблера (Assembler Directive / Pseudo-op)
///
/// Представляет собой инструкцию для самого компилятора ассемблера, а не для процессора.
/// Директивы управляют размещением кода в секциях, экспортом символов, выравниванием памяти
/// и ручным внедрением сырых данных в итоговый объектный файл.
///
/// ## Анатомия в конвейере компиляции
///
/// В отличие от инструкций процессора (`MOV`, `PUSH`), которые на этапе эмиттера превращаются
/// в машинный код (через REX-префиксы, ModR/M и Sib байты), директивы обрабатываются преимущественно
/// на **первом проходе (Pass 1)** ассемблера для формирования структуры объектного файла.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directive {
    /// Тип выполняемой псевдокоманды (например, смена секции или резервирование памяти).
    pub kind: DirectiveKind,

    /// Список аргументов директивы. Операнды директив принципиально отличаются от
    /// операндов процессора, так как могут содержать сырые строки или имена внешних модулей.
    pub operands: Vec<DirectiveOperand>,
}

/// # Категории директив компилятора
///
/// Перечисление всех поддерживаемых типов директив. Включает в себя управление секциями,
/// глобализацию символов для линкера, директивы выделения памяти под константы и выравнивание.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirectiveKind {
    // ==========================================
    // Управление секциями (Section Management)
    // ==========================================
    /// # .text
    ///
    /// Переключает контекст сборщика в секцию кода.
    /// Все последующие инструкции процессора будут транслироваться в байт-код и складываться
    /// именно в этот сегмент. В итоговом ELF-файле эта секция получит флаги `SHF_ALLOC | SHF_EXECINSTR`.
    Text,

    /// # .data
    ///
    /// Переключает контекст сборщика в секцию инициализированных глобальных и статических переменных.
    /// Сюда транслируются директивы выделения памяти (`Byte`, `Word`, `Qword`), имеющие начальное значение.
    /// Получает флаги `SHF_ALLOC | SHF_WRITE`.
    Data,

    /// # .bss
    ///
    /// Переключает контекст в секцию неинициализированных данных (Block Started by Symbol).
    /// Данная секция физически не занимает места в объектном файле на диске, а лишь указывает
    /// операционной системе, сколько памяти нужно выделить и заполнить нулями при старте процесса.
    Bss,

    // ==========================================
    // Экспорт/Импорт символов (Linker Bindings)
    // ==========================================
    /// # .global / .globl / EXPORT
    ///
    /// Делает указанный символ (метку) видимым вне текущего объектного файла.
    /// Ассемблер помечает этот символ в таблице символов (`.symtab`) как `STB_GLOBAL`.
    /// Линкер (ld) использует эти метки для разрешения внешних зависимостей (например, поиск точки входа `_start`).
    Global,

    /// # .extern / IMPORT
    ///
    /// Объявляет, что символ определен в другом модуле (компиляционном юните).
    /// Ассемблер оставляет адрес этого символа нулевым и генерирует запись в таблице релокаций (`.rela.text`),
    /// чтобы линкер прошил правильный адрес во время финальной сборки.
    Extern,

    // ==========================================
    // Выделение памяти и данные (Data Definition)
    // ==========================================
    /// # .byte / db (Define Byte)
    ///
    /// Выделяет 1 байт (8 бит) памяти и инициализирует его числовым значением.
    /// Часто используется для ручного построения таблиц или мелких флагов.
    Byte,

    /// # .word / dw (Define Word)
    ///
    /// Выделяет 2 байта (16 бит / слово) памяти. В архитектуре x86_64 записывается
    /// в память в формате Little-Endian (младший байт по младшему адресу).
    Word,

    /// # .long / .int / dd (Define Doubleword)
    ///
    /// Выделяет 4 байта (32 бита / двойное слово) памяти.
    Dword,

    /// # .quad / dq (Define Quadword)
    ///
    /// Выделяет 8 байт (64 бита / учетверенное слово) памяти. Это базовый размер
    /// указателя и стандартного целочисленного регистра (`RAX`, `RSP`) в режиме x86_64.
    Qword,

    /// # .ascii / .asciz / db "string"
    ///
    /// Внедряет последовательность байт, представляющую ASCII-строку.
    /// При парсинге вариантов типа `.asciz` компилятор должен автоматически добавить
    /// нулевой байт (`0x00`) в конец строки (Null-terminated string).
    Ascii,

    // ==========================================
    // Выравнивание (Alignment)
    // ==========================================
    /// # .align / .balign / ALIGN
    ///
    /// Инструктирует компилятор сдвинуть текущий счетчик адреса (Location Counter / PC)
    /// вперед до тех пор, пока он не станет кратен указанному числу байт (обычно степени двойки: 4, 8, 16, 64).
    ///
    /// * Если выравнивание происходит в секции `.text`, пустые байты заполняются инструкциями `NOP` (`0x90`).
    /// * Если в секции `.data`, то заполняется нулями (`0x00`).
    ///   Критически важно для оптимизации кэша процессора и выравнивания прыжков в циклах.
    Align,
}

/// # Операнды директив ассемблера
///
/// В отличие от вычислительных операндов процессора, операнды директив оперируют
/// мета-данными, необходимыми для разметки объектного файла на этапе сборки.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirectiveOperand {
    /// Числовая константа. Используется для размеров выравнивания (`.align 16`)
    /// или непосредственных данных (`.quad 100`).
    Number(i64),

    /// Строковый литерал. Используется исключительно в директивах внедрения текста (`.ascii "Hello"`).
    StringLiteral(StrId),

    /// Идентификатор символа из пула строк компилятора. Используется для объявления
    /// видимости функций и меток (`.global _start`).
    Symbol(StrId),
}

// =========================================================================
// Реализация парсинга строк в DirectiveKind
// =========================================================================

impl FromStr for DirectiveKind {
    type Err = ISAError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let cleaned = s.to_lowercase();
        let trimmed = cleaned.trim();

        // Удаляем начальную точку, если директива написана в GAS-стиле (`.data` -> `data`)
        let target = trimmed.trim_start_matches('.');

        match target {
            // Секции
            "text" | "code" => Ok(Self::Text),
            "data" => Ok(Self::Data),
            "bss" => Ok(Self::Bss),

            // Линковка и видимость
            "global" | "globl" | "export" => Ok(Self::Global),
            "extern" | "import" => Ok(Self::Extern),

            // Определение данных
            "byte" | "db" => Ok(Self::Byte),
            "word" | "dw" => Ok(Self::Word),
            "dword" | "long" | "int" | "dd" => Ok(Self::Dword),
            "qword" | "quad" | "dq" => Ok(Self::Qword),
            "ascii" | "asciz" | "ascii_str" => Ok(Self::Ascii),

            // Выравнивание
            "align" | "balign" => Ok(Self::Align),

            duck => Err(ISAError::new(ISAErrorKind::UnknownDirective(
                duck.to_string(),
            ))),
        }
    }
}
