use std::{
    borrow::{Borrow, ToOwned},
    collections::HashMap,
    hash::Hash,
};

use crate::Index;

/// Maps user keys to internal `Index` handles.
///
/// Renamed from the former `IndexMap` to avoid colliding with the `indexmap`
/// crate. Power users can promote `&K` to `Index` via `get_or_create` and
/// skip the lookup on subsequent hot-path calls.
#[derive(Debug)]
pub struct KeyTable<K>(HashMap<K, Index>);

impl<K> KeyTable<K>
where
    K: Eq + Hash,
{
    pub fn new() -> Self {
        Self(HashMap::new())
    }

    pub fn get<Q: ?Sized + Hash + Eq>(&self, k: &Q) -> Option<Index>
    where
        K: Borrow<Q>,
    {
        self.0.get(k).cloned()
    }

    pub fn get_or_create<Q: ?Sized + Hash + Eq + ToOwned<Owned = K>>(&mut self, k: &Q) -> Index
    where
        K: Borrow<Q>,
    {
        if let Some(idx) = self.0.get(k) {
            *idx
        } else {
            let idx = Index::from(self.0.len());
            self.0.insert(k.to_owned(), idx);
            idx
        }
    }

    pub fn key(&self, idx: Index) -> Option<&K> {
        self.0
            .iter()
            .find(|&(_, value)| *value == idx)
            .map(|(key, _)| key)
    }

    pub fn keys(&self) -> impl Iterator<Item = &K> {
        self.0.keys()
    }

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

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

impl<K> Default for KeyTable<K>
where
    K: Eq + Hash,
{
    fn default() -> Self {
        KeyTable::new()
    }
}
