//! Plugin-defined component storage.
//!
//! The SDK tracks the 12 behavioral traits in `EntityState`. Plugins that need
//! their *own* typed per-entity data (e.g. Catan's `Hex { resource, number }`)
//! hold a `ComponentStore<T>` in their plugin state. There is deliberately no
//! `Box<dyn Any>` or type-erased storage on `EntityState` — that's the D01
//! decision. Every component lookup is a monomorphized HashMap access.
//!
//! Plugins are responsible for keeping their `ComponentStore<T>` in sync with
//! `store.alive` — call `.remove(id)` in an `on_entity_destroyed_react` handler
//! if the component should be removed when its entity is destroyed.

use std::collections::HashMap;
use crate::types::EntityId;

/// Typed per-entity data owned by a plugin. A thin wrapper over `HashMap<EntityId, T>`
/// that signals intent ("this is plugin-specific per-entity data") and exposes a
/// small stable API.
#[derive(Debug, Clone)]
pub struct ComponentStore<T> {
    data: HashMap<EntityId, T>,
}

// Hand-written `Default` so the impl carries no `T: Default` bound — an empty
// `HashMap<EntityId, T>` exists for any `T` regardless.
impl<T> Default for ComponentStore<T> {
    fn default() -> Self {
        Self { data: HashMap::new() }
    }
}

impl<T> ComponentStore<T> {
    pub fn new() -> Self {
        Self { data: HashMap::new() }
    }

    pub fn insert(&mut self, id: EntityId, value: T) -> Option<T> {
        self.data.insert(id, value)
    }

    pub fn get(&self, id: EntityId) -> Option<&T> {
        self.data.get(&id)
    }

    pub fn get_mut(&mut self, id: EntityId) -> Option<&mut T> {
        self.data.get_mut(&id)
    }

    pub fn remove(&mut self, id: EntityId) -> Option<T> {
        self.data.remove(&id)
    }

    pub fn contains(&self, id: EntityId) -> bool {
        self.data.contains_key(&id)
    }

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

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

    pub fn iter(&self) -> impl Iterator<Item = (&EntityId, &T)> {
        self.data.iter()
    }

    pub fn ids(&self) -> impl Iterator<Item = &EntityId> {
        self.data.keys()
    }

    pub fn values(&self) -> impl Iterator<Item = &T> {
        self.data.values()
    }

    /// Clear out entries whose entities are no longer in the alive set.
    pub fn prune<F: Fn(EntityId) -> bool>(&mut self, is_alive: F) {
        self.data.retain(|id, _| is_alive(*id));
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn insert_get_remove() {
        let mut cs = ComponentStore::<u32>::new();
        cs.insert(EntityId::from_raw(1), 42);
        assert_eq!(cs.get(EntityId::from_raw(1)), Some(&42));
        assert!(cs.contains(EntityId::from_raw(1)));
        assert_eq!(cs.len(), 1);

        let taken = cs.remove(EntityId::from_raw(1));
        assert_eq!(taken, Some(42));
        assert!(cs.is_empty());
    }

    #[test]
    fn prune_removes_dead_entities() {
        let mut cs = ComponentStore::<&'static str>::new();
        cs.insert(EntityId::from_raw(1), "a");
        cs.insert(EntityId::from_raw(2), "b");
        cs.insert(EntityId::from_raw(3), "c");

        cs.prune(|id| id == EntityId::from_raw(1) || id == EntityId::from_raw(3));
        assert_eq!(cs.len(), 2);
        assert!(cs.contains(EntityId::from_raw(1)));
        assert!(!cs.contains(EntityId::from_raw(2)));
        assert!(cs.contains(EntityId::from_raw(3)));
    }

    #[test]
    fn default_compiles_without_t_default_bound() {
        struct NoDefault(#[allow(dead_code)] u32);
        let cs: ComponentStore<NoDefault> = ComponentStore::default();
        assert!(cs.is_empty());
    }
}
