use crate::{Index, time_slice::Skill};

/// Dense Vec-backed store for per-agent skill state within a TimeSlice.
///
/// Indexed directly by Index.0, eliminating HashMap hashing in the inner
/// convergence loop. Uses a parallel `present` mask so iteration skips
/// absent slots without incurring per-slot Option overhead in the hot path.
#[derive(Debug, Default)]
pub struct SkillStore {
    skills: Vec<Skill>,
    present: Vec<bool>,
    n_present: usize,
}

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

    fn ensure_capacity(&mut self, idx: usize) {
        if idx >= self.skills.len() {
            self.skills.resize_with(idx + 1, Skill::default);
            self.present.resize(idx + 1, false);
        }
    }

    pub fn insert(&mut self, idx: Index, skill: Skill) {
        self.ensure_capacity(idx.0);
        if !self.present[idx.0] {
            self.n_present += 1;
        }
        self.skills[idx.0] = skill;
        self.present[idx.0] = true;
    }

    pub fn get(&self, idx: Index) -> Option<&Skill> {
        if idx.0 < self.present.len() && self.present[idx.0] {
            Some(&self.skills[idx.0])
        } else {
            None
        }
    }

    pub fn get_mut(&mut self, idx: Index) -> Option<&mut Skill> {
        if idx.0 < self.present.len() && self.present[idx.0] {
            Some(&mut self.skills[idx.0])
        } else {
            None
        }
    }

    #[allow(dead_code)]
    pub fn contains(&self, idx: Index) -> bool {
        idx.0 < self.present.len() && self.present[idx.0]
    }

    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.n_present
    }

    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        self.n_present == 0
    }

    pub fn iter(&self) -> impl Iterator<Item = (Index, &Skill)> {
        self.present.iter().enumerate().filter_map(|(i, &p)| {
            if p {
                Some((Index(i), &self.skills[i]))
            } else {
                None
            }
        })
    }

    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Skill)> {
        self.skills
            .iter_mut()
            .zip(self.present.iter())
            .enumerate()
            .filter_map(|(i, (s, &p))| if p { Some((Index(i), s)) } else { None })
    }

    pub fn keys(&self) -> impl Iterator<Item = Index> + '_ {
        self.present
            .iter()
            .enumerate()
            .filter_map(|(i, &p)| if p { Some(Index(i)) } else { None })
    }
}

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

    #[test]
    fn insert_then_get() {
        let mut store = SkillStore::new();
        let idx = Index(3);
        store.insert(idx, Skill::default());
        assert!(store.contains(idx));
        assert_eq!(store.len(), 1);
        assert!(store.get(idx).is_some());
    }

    #[test]
    fn missing_returns_none() {
        let store = SkillStore::new();
        assert!(store.get(Index(0)).is_none());
        assert!(!store.contains(Index(42)));
    }

    #[test]
    fn iter_skips_absent_slots() {
        let mut store = SkillStore::new();
        store.insert(Index(0), Skill::default());
        store.insert(Index(5), Skill::default());
        let keys: Vec<Index> = store.keys().collect();
        assert_eq!(keys, vec![Index(0), Index(5)]);
    }

    #[test]
    fn double_insert_does_not_double_count() {
        let mut store = SkillStore::new();
        store.insert(Index(2), Skill::default());
        store.insert(Index(2), Skill::default());
        assert_eq!(store.len(), 1);
    }
}
