use super::Value;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::ops::Index;
use std::sync::{Arc, OnceLock, RwLock};

const RECORD_INDEX_THRESHOLD: usize = 8;
const RECORD_INLINE_CAPACITY: usize = 4;

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Symbol(u32);

#[derive(Default)]
struct SymbolTable {
    lookup: FxHashMap<Arc<str>, Symbol>,
    names: Vec<Arc<str>>,
}

fn symbol_table() -> &'static RwLock<SymbolTable> {
    static TABLE: OnceLock<RwLock<SymbolTable>> = OnceLock::new();
    TABLE.get_or_init(|| RwLock::new(SymbolTable::default()))
}

pub(crate) fn lookup_symbol(name: &str) -> Option<Symbol> {
    symbol_table()
        .read()
        .expect("symbol table read lock poisoned")
        .lookup
        .get(name)
        .copied()
}

pub(crate) fn intern_symbol(name: &str) -> Symbol {
    intern_symbol_with_name(name).0
}

pub(crate) fn intern_symbol_with_name(name: &str) -> (Symbol, Arc<str>) {
    {
        let table = symbol_table()
            .read()
            .expect("symbol table read lock poisoned");
        if let Some(symbol) = table.lookup.get(name) {
            return (*symbol, table.names[symbol.0 as usize].clone());
        }
    }

    let mut table = symbol_table()
        .write()
        .expect("symbol table write lock poisoned");
    if let Some(symbol) = table.lookup.get(name) {
        return (*symbol, table.names[symbol.0 as usize].clone());
    }

    let symbol = Symbol(table.names.len() as u32);
    let text: Arc<str> = Arc::<str>::from(name);
    table.names.push(text.clone());
    table.lookup.insert(text.clone(), symbol);
    (symbol, text)
}

pub(crate) fn symbol_name(symbol: Symbol) -> Arc<str> {
    symbol_table()
        .read()
        .expect("symbol table read lock poisoned")
        .names[symbol.0 as usize]
        .clone()
}

#[derive(Clone, Debug, PartialEq)]
pub(super) struct RecordEntry {
    pub(super) symbol: Symbol,
    pub(super) name: Arc<str>,
    pub(super) value: Value,
}

#[derive(Clone, Debug, Default)]
pub struct Record {
    pub(super) entries: SmallVec<[RecordEntry; RECORD_INLINE_CAPACITY]>,
    index: Option<FxHashMap<Symbol, usize>>,
}

impl Record {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            entries: SmallVec::with_capacity(capacity),
            index: (capacity > RECORD_INDEX_THRESHOLD)
                .then(|| FxHashMap::with_capacity_and_hasher(capacity, Default::default())),
        }
    }

    pub fn len(&self) -> usize {
        self.entries.len()
    }

    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    pub fn get(&self, name: &str) -> Option<&Value> {
        self.get_symbol(lookup_symbol(name)?)
    }

    pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
        let symbol = lookup_symbol(name)?;
        let index = self.position_for(symbol)?;
        Some(&mut self.entries[index].value)
    }

    pub fn remove(&mut self, name: &str) -> Option<Value> {
        let symbol = lookup_symbol(name)?;
        self.remove_symbol(symbol)
    }

    pub fn insert(&mut self, name: String, value: Value) -> Option<Value> {
        let (symbol, name) = intern_symbol_with_name(&name);
        self.insert_symbolized(symbol, name, value)
    }

    pub fn insert_str(&mut self, name: &str, value: Value) -> Option<Value> {
        let (symbol, name) = intern_symbol_with_name(name);
        self.insert_symbolized(symbol, name, value)
    }

    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
        self.entries
            .iter()
            .map(|entry| (entry.name.as_ref(), &entry.value))
    }

    pub fn keys(&self) -> impl Iterator<Item = &str> {
        self.entries.iter().map(|entry| entry.name.as_ref())
    }

    pub fn values(&self) -> impl Iterator<Item = &Value> {
        self.entries.iter().map(|entry| &entry.value)
    }

    pub(crate) fn get_symbol(&self, symbol: Symbol) -> Option<&Value> {
        let index = self.position_for(symbol)?;
        Some(&self.entries[index].value)
    }

    pub(crate) fn get_symbol_mut(&mut self, symbol: Symbol) -> Option<&mut Value> {
        let index = self.position_for(symbol)?;
        Some(&mut self.entries[index].value)
    }

    pub(crate) fn insert_symbolized(
        &mut self,
        symbol: Symbol,
        name: Arc<str>,
        value: Value,
    ) -> Option<Value> {
        if let Some(index) = self.position_for(symbol) {
            return Some(std::mem::replace(&mut self.entries[index].value, value));
        }

        let index = self.entries.len();
        self.entries.push(RecordEntry {
            symbol,
            name,
            value,
        });
        self.reindex_after_insert(index);
        None
    }

    pub(super) fn remove_symbol(&mut self, symbol: Symbol) -> Option<Value> {
        let index = self.position_for(symbol)?;
        let removed = self.entries.swap_remove(index);
        self.reindex_after_remove(symbol, index);
        Some(removed.value)
    }

    fn position_for(&self, symbol: Symbol) -> Option<usize> {
        if let Some(index) = &self.index {
            return index.get(&symbol).copied();
        }
        self.entries.iter().position(|entry| entry.symbol == symbol)
    }

    fn rebuild_index(&mut self) {
        self.index = (self.entries.len() > RECORD_INDEX_THRESHOLD).then(|| {
            let mut index =
                FxHashMap::with_capacity_and_hasher(self.entries.len(), Default::default());
            for (slot, entry) in self.entries.iter().enumerate() {
                index.insert(entry.symbol, slot);
            }
            index
        });
    }

    fn reindex_after_insert(&mut self, index: usize) {
        if let Some(map) = &mut self.index {
            map.insert(self.entries[index].symbol, index);
            return;
        }
        if self.entries.len() > RECORD_INDEX_THRESHOLD {
            self.rebuild_index();
        }
    }

    fn reindex_after_remove(&mut self, removed: Symbol, index: usize) {
        if self.entries.len() <= RECORD_INDEX_THRESHOLD {
            self.index = None;
            return;
        }

        let Some(map) = &mut self.index else {
            self.rebuild_index();
            return;
        };
        map.remove(&removed);
        if let Some(moved) = self.entries.get(index) {
            map.insert(moved.symbol, index);
        }
    }
}

impl Index<&str> for Record {
    type Output = Value;

    fn index(&self, name: &str) -> &Self::Output {
        self.get(name)
            .unwrap_or_else(|| panic!("missing record key `{name}`"))
    }
}

impl PartialEq for Record {
    fn eq(&self, other: &Self) -> bool {
        if self.len() != other.len() {
            return false;
        }
        self.entries.iter().all(|entry| {
            other
                .get_symbol(entry.symbol)
                .is_some_and(|value| value == &entry.value)
        })
    }
}

impl FromIterator<(String, Value)> for Record {
    fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
        let iter = iter.into_iter();
        let (lower, _) = iter.size_hint();
        let mut record = Record::with_capacity(lower);
        for (name, value) in iter {
            record.insert(name, value);
        }
        record
    }
}

impl Serialize for Record {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeMap;

        let mut map = serializer.serialize_map(Some(self.entries.len()))?;
        for entry in &self.entries {
            map.serialize_entry(entry.name.as_ref(), &entry.value)?;
        }
        map.end()
    }
}

impl<'de> Deserialize<'de> for Record {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let map = FxHashMap::<String, Value>::deserialize(deserializer)?;
        Ok(map.into_iter().collect())
    }
}

pub(crate) fn record_with_capacity(capacity: usize) -> Record {
    Record::with_capacity(capacity)
}
