//! Selection — core-owned view state.
//!
//! A selection is a **set of [`RowId`]s**, never an `Option<RowId>` (
//! cross-view linked selection sets multiple sibling rows from one click;
//! single-select grids simply hold a set of size ≤ 1). It is identity-keyed, so
//! it survives re-sort, re-filter-to-visible, and re-virtualization by
//! construction — position is never stored. And it is **settable
//! programmatically from outside** (the primitive linked selection and presets
//! build on), not only via user clicks.
//!
//! [`RowId`]: crate::data::DataSource::RowId

use core::hash::Hash;
use std::collections::HashSet;

/// A set of selected row identities.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Selection<Id: Eq + Hash + Clone> {
    ids: HashSet<Id>,
}

impl<Id: Eq + Hash + Clone> Default for Selection<Id> {
    fn default() -> Self {
        Self::new()
    }
}

impl<Id: Eq + Hash + Clone> Selection<Id> {
    /// An empty selection.
    #[must_use]
    pub fn new() -> Self {
        Self { ids: HashSet::new() }
    }

    /// Number of selected rows.
    #[must_use]
    pub fn len(&self) -> usize {
        self.ids.len()
    }

    /// Whether nothing is selected.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.ids.is_empty()
    }

    /// Whether `id` is selected.
    #[must_use]
    pub fn contains(&self, id: &Id) -> bool {
        self.ids.contains(id)
    }

    /// Iterates the selected ids (unordered).
    pub fn iter(&self) -> impl Iterator<Item = &Id> {
        self.ids.iter()
    }

    /// Adds `id` to the selection.
    pub fn select(&mut self, id: Id) {
        let _ = self.ids.insert(id);
    }

    /// Removes `id` from the selection.
    pub fn deselect(&mut self, id: &Id) {
        let _ = self.ids.remove(id);
    }

    /// Toggles `id`'s membership.
    pub fn toggle(&mut self, id: Id) {
        if !self.ids.remove(&id) {
            let _ = self.ids.insert(id);
        }
    }

    /// Replaces the whole selection with exactly `id` — the single-select
    /// gesture (click, keyboard nav).
    pub fn replace(&mut self, id: Id) {
        self.ids.clear();
        let _ = self.ids.insert(id);
    }

    /// Clears the selection.
    pub fn clear(&mut self) {
        self.ids.clear();
    }

    /// Replaces the selection with `ids` — the programmatic entry point linked
    /// selection uses (one click in a sibling view selects several rows here).
    pub fn set(&mut self, ids: impl IntoIterator<Item = Id>) {
        self.ids = ids.into_iter().collect();
    }

    /// Keeps only the ids for which `keep` returns true (prune-on-filter,
    /// the accepted default).
    pub fn retain(&mut self, keep: impl FnMut(&Id) -> bool) {
        self.ids.retain(keep);
    }
}

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

    #[test]
    fn select_toggle_deselect() {
        let mut s: Selection<&str> = Selection::new();
        assert!(s.is_empty());
        s.select("a");
        s.select("a"); // idempotent
        s.select("b");
        assert_eq!(s.len(), 2);
        assert!(s.contains(&"a") && s.contains(&"b"));
        s.toggle("a");
        assert!(!s.contains(&"a"));
        s.toggle("a");
        assert!(s.contains(&"a"));
        s.deselect(&"b");
        assert!(!s.contains(&"b"));
    }

    #[test]
    fn replace_is_single_select() {
        let mut s: Selection<u64> = Selection::new();
        s.set([1, 2, 3]);
        assert_eq!(s.len(), 3);
        s.replace(9);
        assert_eq!(s.len(), 1);
        assert!(s.contains(&9));
    }

    #[test]
    fn set_and_retain_for_linked_selection_and_pruning() {
        let mut s: Selection<u64> = Selection::new();
        // Programmatic multi-set (linked selection shape).
        s.set([10, 20, 30]);
        assert_eq!(s.len(), 3);
        // Prune-on-filter keeps only survivors.
        s.retain(|id| *id >= 20);
        assert_eq!(s.len(), 2);
        assert!(!s.contains(&10));
    }
}
