//! [`GroupRegistry`]: project-defined gameplay group names.
//!
//! Groups are the **multi-valued** counterpart to the single-valued
//! [`Layer`](super::Layer). Where an entity is on exactly one layer (its
//! render/physics filter slot), it can belong to *any number* of groups —
//! `"Enemies"`, `"Interactables"`, `"SaveOnExit"` — which game code and scripts
//! query by name. This mirrors the Unity model: one Layer + many tags/groups.
//!
//! Per-entity membership is stored in the [`Tags`](super::Tags) component. The
//! registry is the project-level list of *which group names exist*, so the
//! editor can offer a fixed set to pick from (predefined, not free-typed) and a
//! team shares one vocabulary. Defining or deleting a group only changes that
//! vocabulary; it never touches the tags already on entities (a deleted group
//! simply becomes an "ungrouped" tag until removed).

use std::collections::BTreeSet;

use serde::{Deserialize, Serialize};

/// The set of project-defined group names.
///
/// Stored sorted (a [`BTreeSet`]) so the editor's dropdown order and serialized
/// form are deterministic. Names are the identity used in data and UI, so they
/// should be stable across a project's life.
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct GroupRegistry {
    names: BTreeSet<String>,
}

impl GroupRegistry {
    /// An empty registry — no groups defined yet.
    pub fn new() -> Self {
        Self::default()
    }

    /// Defines `name` as a group. Returns `true` if it was newly added.
    pub fn define(&mut self, name: impl Into<String>) -> bool {
        self.names.insert(name.into())
    }

    /// Removes `name` from the defined groups. Returns `true` if it existed.
    ///
    /// Entities already tagged with `name` keep the tag — only the project's
    /// list of valid groups shrinks.
    pub fn undefine(&mut self, name: &str) -> bool {
        self.names.remove(name)
    }

    /// Whether `name` is a defined group.
    pub fn contains(&self, name: &str) -> bool {
        self.names.contains(name)
    }

    /// Iterates the defined group names in sorted order.
    pub fn iter(&self) -> impl Iterator<Item = &str> {
        self.names.iter().map(String::as_str)
    }

    /// The number of defined groups.
    pub fn len(&self) -> usize {
        self.names.len()
    }

    /// Whether no groups are defined.
    pub fn is_empty(&self) -> bool {
        self.names.is_empty()
    }
}

impl<S: Into<String>> FromIterator<S> for GroupRegistry {
    fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
        GroupRegistry {
            names: iter.into_iter().map(Into::into).collect(),
        }
    }
}

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

    #[test]
    fn define_is_idempotent_and_reports_newness() {
        let mut reg = GroupRegistry::new();
        assert!(reg.is_empty());
        assert!(reg.define("Enemies"));
        assert!(!reg.define("Enemies")); // already defined
        assert!(reg.define("Pickups"));
        assert!(reg.contains("Enemies"));
        assert_eq!(reg.len(), 2);
    }

    #[test]
    fn undefine_removes_only_from_the_vocabulary() {
        let mut reg: GroupRegistry = ["Enemies", "Pickups"].into_iter().collect();
        assert!(reg.undefine("Enemies"));
        assert!(!reg.undefine("Enemies"));
        assert!(!reg.contains("Enemies"));
        assert!(reg.contains("Pickups"));
    }

    #[test]
    fn iter_is_sorted() {
        let reg: GroupRegistry = ["Zed", "Alpha", "Mid"].into_iter().collect();
        assert_eq!(reg.iter().collect::<Vec<_>>(), vec!["Alpha", "Mid", "Zed"]);
    }

    #[test]
    fn round_trips_through_ron() {
        let reg: GroupRegistry = ["Enemies", "Interactables"].into_iter().collect();
        let ron = ron::to_string(&reg).unwrap();
        let back: GroupRegistry = ron::from_str(&ron).unwrap();
        assert_eq!(reg, back);
    }
}
