//! Объектная модель результата сборки.
//!
//! Этот модуль описывает промежуточный формат между кодогенератором и
//! потребителями: JIT, AOT writer-ами, CLI и библиотечным API.

use crate::codegen::reloc::RelocKind;

/// Идентификатор секции внутри [`CompiledModule`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SectionId(pub usize);

/// Тип секции.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionKind {
    Text,
    Data,
    Bss,
    Custom,
}

/// Секция с байтами или зарезервированным размером.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Section {
    pub id: SectionId,
    pub name: String,
    pub kind: SectionKind,
    pub bytes: Vec<u8>,
    pub align: usize,
    pub virtual_size: usize,
}

impl Section {
    pub fn new(
        id: SectionId,
        name: impl Into<String>,
        kind: SectionKind,
        bytes: Vec<u8>,
        align: usize,
    ) -> Self {
        let virtual_size = bytes.len();

        Self {
            id,
            name: name.into(),
            kind,
            bytes,
            align,
            virtual_size,
        }
    }
}

/// Видимость символа для будущей линковки.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolVisibility {
    Local,
    Global,
    External,
}

/// Символ модуля: метка, экспорт или внешний символ.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Symbol {
    pub name: String,
    pub section: Option<SectionId>,
    pub offset: usize,
    pub visibility: SymbolVisibility,
}

impl Symbol {
    pub fn defined(
        name: impl Into<String>,
        section: SectionId,
        offset: usize,
        visibility: SymbolVisibility,
    ) -> Self {
        Self {
            name: name.into(),
            section: Some(section),
            offset,
            visibility,
        }
    }

    pub fn local(name: impl Into<String>, section: SectionId, offset: usize) -> Self {
        Self::defined(name, section, offset, SymbolVisibility::Local)
    }

    pub fn external(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            section: None,
            offset: 0,
            visibility: SymbolVisibility::External,
        }
    }
}

/// Импортируемый символ из внешнего модуля.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Import {
    pub module: String,
    pub name: String,
    pub alias: String,
}

impl Import {
    pub fn new(
        module: impl Into<String>,
        name: impl Into<String>,
        alias: impl Into<String>,
    ) -> Self {
        Self {
            module: module.into(),
            name: name.into(),
            alias: alias.into(),
        }
    }
}

/// Релокация, привязанная к секции и символу назначения.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObjectRelocation {
    pub section: SectionId,
    pub offset: usize,
    pub kind: RelocKind,
    pub target: String,
}

/// Выбранная точка входа.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntryPoint {
    pub symbol: String,
    pub section: SectionId,
    pub offset: usize,
}

/// Скомпилированный модуль, пригодный для JIT/AOT.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompiledModule {
    pub sections: Vec<Section>,
    pub symbols: Vec<Symbol>,
    pub relocations: Vec<ObjectRelocation>,
    pub imports: Vec<Import>,
    pub entry: Option<EntryPoint>,
}

impl CompiledModule {
    pub fn new(
        sections: Vec<Section>,
        symbols: Vec<Symbol>,
        relocations: Vec<ObjectRelocation>,
    ) -> Self {
        Self::with_imports(sections, symbols, relocations, Vec::new())
    }

    pub fn with_imports(
        sections: Vec<Section>,
        mut symbols: Vec<Symbol>,
        relocations: Vec<ObjectRelocation>,
        mut imports: Vec<Import>,
    ) -> Self {
        symbols.sort_by(|a, b| a.name.cmp(&b.name));
        imports.sort_by(|a, b| {
            a.module
                .cmp(&b.module)
                .then_with(|| a.name.cmp(&b.name))
                .then_with(|| a.alias.cmp(&b.alias))
        });
        imports.dedup_by(|a, b| a.module == b.module && a.name == b.name && a.alias == b.alias);

        let mut module = Self {
            sections,
            symbols,
            relocations,
            imports,
            entry: None,
        };
        module.entry = module.find_entry(["_start", "main"]);
        module
    }

    /// Возвращает секцию по идентификатору.
    pub fn section(&self, id: SectionId) -> Option<&Section> {
        self.sections.get(id.0)
    }

    /// Возвращает первую секцию с указанным именем.
    pub fn section_by_name(&self, name: &str) -> Option<&Section> {
        self.sections.iter().find(|section| section.name == name)
    }

    /// Возвращает `.text`, если она есть.
    pub fn text_section(&self) -> Option<&Section> {
        self.section_by_name(".text")
    }

    /// Возвращает байты `.text` для текущих JIT/AOT потребителей.
    pub fn text_bytes(&self) -> &[u8] {
        self.text_section()
            .map(|section| section.bytes.as_slice())
            .unwrap_or(&[])
    }

    /// Ищет символ по имени.
    pub fn symbol(&self, name: &str) -> Option<&Symbol> {
        self.symbols.iter().find(|symbol| symbol.name == name)
    }

    /// Ищет импорт по имени, используемому в ассемблерном коде.
    pub fn import(&self, alias: &str) -> Option<&Import> {
        self.imports.iter().find(|import| import.alias == alias)
    }

    /// Ищет точку входа по списку кандидатов.
    pub fn find_entry<const N: usize>(&self, names: [&str; N]) -> Option<EntryPoint> {
        names.into_iter().find_map(|name| {
            let symbol = self.symbol(name)?;
            let section = symbol.section?;

            Some(EntryPoint {
                symbol: symbol.name.clone(),
                section,
                offset: symbol.offset,
            })
        })
    }
}
