//! Per-entity [`Layer`] membership and gameplay [`Tags`].

use std::collections::BTreeSet;

use serde::{Deserialize, Serialize};

use super::LayerMask;

/// Component: the **single** layer an entity is on.
///
/// Each entity belongs to exactly one of 32 logical layers (index `0..32`).
/// Filters elsewhere — a camera's visibility mask, a physics collision filter,
/// a raycast's layer filter — carry [`LayerMask`]s and select an entity by
/// testing `mask.contains_layer(entity.layer.index)` (see [`Self::matches`]).
///
/// This matches the Unity model: **per-entity membership is single, filters
/// are masks.** If you need an entity to be "in" multiple categories
/// simultaneously, use [`Tags`] (gameplay tags) — tagging is the multi-valued
/// concept; layers are the single-valued one.
///
/// Every freshly spawned entity is on [`DEFAULT`](Self::DEFAULT) (the layer
/// named `"Default"` at index 0) unless changed, so it's visible to "see
/// everything" filters out of the box.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, crate::reflect::Reflect,
)]
pub struct Layer {
    /// Layer index (`0..32`). Use the
    /// [`LayerRegistry`](super::LayerRegistry) to translate between this and a
    /// human-readable name.
    pub index: u32,
}

impl Layer {
    /// The "Default" layer (index 0). Every freshly spawned entity starts here.
    pub const DEFAULT: Layer = Layer { index: 0 };

    /// Builds a `Layer` on the given `index` (`0..32`).
    pub const fn on(index: u32) -> Self {
        Self { index }
    }

    /// Whether this layer is selected by the given filter mask.
    pub const fn matches(self, filter: LayerMask) -> bool {
        filter.contains_layer(self.index)
    }

    /// A [`LayerMask`] containing exactly this layer — useful when an API
    /// expects a mask (e.g. a one-layer camera visibility filter).
    pub const fn mask(self) -> LayerMask {
        LayerMask::layer(self.index)
    }
}

impl Default for Layer {
    fn default() -> Self {
        Layer::DEFAULT
    }
}

/// Component: free-form gameplay tags on an entity.
///
/// Tags are the lightweight, string-keyed counterpart to [`Layer`]. Where a
/// [`LayerMask`] is a fixed 32-slot bitset for hot-path *filtering*, tags are an
/// open-ended set for *identification* — `"Enemy"`, `"Interactable"`,
/// `"Checkpoint"` — that game code and scripts query by name. Stored sorted so
/// serialization is deterministic.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tags(BTreeSet<String>);

impl Tags {
    /// An empty tag set.
    pub fn new() -> Self {
        Self::default()
    }

    /// A tag set containing the single tag `tag`.
    pub fn single(tag: impl Into<String>) -> Self {
        let mut set = BTreeSet::new();
        set.insert(tag.into());
        Tags(set)
    }

    /// Adds `tag`. Returns `true` if it was not already present.
    pub fn insert(&mut self, tag: impl Into<String>) -> bool {
        self.0.insert(tag.into())
    }

    /// Removes `tag`. Returns `true` if it was present.
    pub fn remove(&mut self, tag: &str) -> bool {
        self.0.remove(tag)
    }

    /// Whether `tag` is present.
    pub fn contains(&self, tag: &str) -> bool {
        self.0.contains(tag)
    }

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

    /// The number of tags.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Whether there are no tags.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

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

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

    #[test]
    fn default_layer_is_zero() {
        let l = Layer::default();
        assert_eq!(l.index, 0);
        // A "see everything" filter selects a default entity.
        assert!(l.matches(LayerMask::ALL));
    }

    #[test]
    fn layer_matches_filter_when_index_is_in_the_mask() {
        let on_npc = Layer::on(2);
        let npc_or_player = LayerMask::NONE.with(1).with(2);
        assert!(on_npc.matches(npc_or_player));
        assert!(!on_npc.matches(LayerMask::layer(5)));
        assert_eq!(on_npc.mask(), LayerMask::layer(2));
    }

    #[test]
    fn layer_round_trips_through_ron() {
        let l = Layer::on(7);
        let ron = ron::to_string(&l).unwrap();
        let back: Layer = ron::from_str(&ron).unwrap();
        assert_eq!(l, back);
    }

    #[test]
    fn tags_insert_remove_contains() {
        let mut tags = Tags::new();
        assert!(tags.insert("Enemy"));
        assert!(!tags.insert("Enemy")); // already present
        assert!(tags.insert("Flying"));
        assert!(tags.contains("Enemy"));
        assert_eq!(tags.len(), 2);
        assert!(tags.remove("Enemy"));
        assert!(!tags.contains("Enemy"));
        assert!(!tags.remove("Enemy"));
    }

    #[test]
    fn tags_iterate_sorted_and_round_trip() {
        let tags: Tags = ["Zebra", "Apple", "Mango"].into_iter().collect();
        assert_eq!(
            tags.iter().collect::<Vec<_>>(),
            vec!["Apple", "Mango", "Zebra"]
        );
        let ron = ron::to_string(&tags).unwrap();
        let back: Tags = ron::from_str(&ron).unwrap();
        assert_eq!(tags, back);
    }
}
