// rasm-core/src/codegen/jit/symbol.rs
//
//! # Резолвинг внешних символов для JIT
//!
//! Когда ассемблерный код делает `call printf` или `call malloc` — это внешние символы,
//! которые живут в libc или других разделяемых библиотеках. JIT-компилятор должен
//! узнать их **реальный адрес в памяти** прямо во время выполнения.
//!
//! ## Как это работает
//!
//! `dlsym(RTLD_DEFAULT, "printf")` — стандартный POSIX-вызов, который ищет символ
//! во всех уже загруженных разделяемых библиотеках (libc, libm, и т.д.).
//! Возвращает указатель на функцию или `null`, если символ не найден.
//!
//! После получения адреса он прошивается в байтовый буфер JIT через механизм релокаций
//! (см. `reloc.rs`), заменяя заглушку `0x00000000`.
use std::collections::HashMap;

/// Таблица внешних символов: `"printf" -> 0x7f3a2b1c0000` (реальный адрес в памяти).
#[derive(Debug, Default)]
pub struct SymbolTable {
    resolved: HashMap<String, usize>,
}

impl SymbolTable {
    pub fn new() -> Self {
        Self {
            resolved: HashMap::new(),
        }
    }

    /// Ищет символ по имени.
    ///
    /// Сначала смотрит в кэш (`resolved`). Если не нашёл — вызывает `dlsym`.
    /// Возвращает `None`, если символ не найден ни в кэше, ни в загруженных библиотеках.
    pub fn resolve(&mut self, name: &str) -> Option<usize> {
        if let Some(&addr) = self.resolved.get(name) {
            return Some(addr);
        }

        let addr = self.dlsym_lookup(name)?;
        self.resolved.insert(name.to_string(), addr);
        Some(addr)
    }

    /// Добавляет символ вручную — полезно для регистрации Rust-функций,
    /// которые должны быть вызываемы из JIT-кода.
    ///
    /// ```ignore
    /// extern "C" fn my_print(n: i64) { println!("{n}"); }
    /// symbol_table.register("my_print", my_print as usize);
    /// ```
    pub fn register(&mut self, name: &str, addr: usize) {
        self.resolved.insert(name.to_string(), addr);
    }

    /// Выполняет поиск через `dlsym(RTLD_DEFAULT, name)`.
    /// Только для UNIX (Linux, macOS). На Windows нужен `GetProcAddress`.
    #[cfg(unix)]
    fn dlsym_lookup(&self, name: &str) -> Option<usize> {
        // SAFETY: dlsym — стандартный POSIX API. CString гарантирует нулевой терминатор.
        // RTLD_DEFAULT = null ptr — ищет во всех загруженных библиотеках.
        unsafe {
            let c_name = std::ffi::CString::new(name).ok()?;
            let ptr = libc::dlsym(libc::RTLD_DEFAULT, c_name.as_ptr());
            if ptr.is_null() {
                None
            } else {
                Some(ptr as usize)
            }
        }
    }

    #[cfg(not(unix))]
    const fn dlsym_lookup(&self, _name: &str) -> Option<usize> {
        // На Windows потребуется реализация через GetProcAddress / LoadLibrary.
        // Пока возвращаем None — внешние символы не поддерживаются.
        None
    }
}
