// rasm-core/src/str_pool.rs
//
//! # Пул строк (String Interner)
//!
//! Высокопроизводительный механизм для хранения уникальных текстовых строк (идентификаторов,
//! мнемоник, меток), используемый компилятором для минимизации аллокаций в куче.
//!
//! ## Как это работает
//! Вместо того чтобы дублировать строку `"rax"` тысячи раз в AST и токенах, компилятор
//! сохраняет её в пул один раз и получает легковесный числовой идентификатор [`StrId`].
//! Все последующие сравнения строк превращаются в сравнение обычных целых чисел,
//! что работает со скоростью процессора.
//!
//! ## Архитектура
//! Под капотом используется единый непрерывный текстовый буфер `String` (`data`) и
//! ассоциативная карта `HashMap` для быстрого поиска `O(1)`. Это предотвращает фрагментацию памяти
//! и делает доступ к данным дружелюбным к кэшу процессора (L1/L2 Cache).
use std::collections::HashMap;

use crate::src::SourceCode;

/// # Идентификатор строки
///
/// Компактная, подлежащая бесплатному копированию обёртка вокруг индекса строки.
/// Заменяет собой тяжелые [`String`] и `&str` внутри токенов [`TokenKind`] и AST.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub struct StrId(pub u32);

#[derive(Debug, Clone, Default)]
pub struct StrPool {
    /// Хранилище строк. Индекс в векторе — это и есть наш StrId.
    /// Box<str> гарантирует отсутствие лишнего оверхеда по емкости (всего 16 байт на элемент).
    storage: Vec<Box<str>>,

    /// Карта для быстрого поиска. Чтобы не хранить строки дважды и не ломать lifetimes,
    /// мы храним хэш строки, который указывает на её индекс в storage.
    /// Однако для поиска нам нужен способ сравнивать строки. Чтобы избежать unsafe,
    /// мы временно интернируем через стандартные механизмы владения.
    lookup: HashMap<String, StrId>,
}

impl StrPool {
    /// Создает пул строк, заранее оптимизированный под размер конкретного исходного кода.
    /// Это предотвращает внутренние перестройки HashMap (rehash) и реаллокации вектора.
    pub fn from_source(src: &SourceCode) -> Self {
        let code_len = src.source.len();

        // Эвристика: в среднем одно слово в ассемблере занимает около 5-7 символов (включая пробелы).
        // Дополнительно берем количество строк как ориентир для уникальных идентификаторов/меток.
        let estimated_lines = src.line_starts.len();
        let estimated_words = (code_len / 6).max(estimated_lines);

        // Чтобы HashMap не перестраивался слишком часто,
        // лучше заложить начальную емкость с небольшим запасом.
        let capacity = estimated_words.max(32);

        Self {
            storage: Vec::with_capacity(capacity),
            lookup: HashMap::with_capacity(capacity),
        }
    }

    pub fn with_capacity(items: usize) -> Self {
        Self {
            storage: Vec::with_capacity(items),
            lookup: HashMap::with_capacity(items),
        }
    }

    /// Интернирует строку. Работает за O(1) в случае успеха.
    pub fn intern(&mut self, s: &str) -> StrId {
        // Если строка уже есть, просто возвращаем её ID
        if let Some(&id) = self.lookup.get(s) {
            return id;
        }

        // Если строки нет:
        let id = StrId(self.storage.len() as u32);

        // Превращаем в оптимальный Box<str> и сохраняем
        let boxed_str = s.to_string().into_boxed_str();
        self.storage.push(boxed_str);

        // Дублируем в карту для поиска (увы, HashMap в Rust требует владения ключом,
        // если мы не хотим писать сложные кастомные хэшеры или использовать unsafe).
        self.lookup.insert(s.to_string(), id);

        id
    }

    /// Мгновенный доступ к строке по ID за O(1) без хэширования
    #[inline]
    pub fn get(&self, id: StrId) -> &str {
        &self.storage[id.0 as usize]
    }
}
