flow_gates/
scope.rs

1use crate::hierarchy::GateHierarchy;
2use crate::types::{Gate, GateMode};
3use std::sync::Arc;
4
5/// Builder for querying and filtering gates
6///
7/// `GateQuery` provides a fluent API for filtering gates by various criteria
8/// such as parameters, scope, and other properties.
9///
10/// # Example
11///
12/// ```rust,no_run
13/// use flow_gates::{GateQuery, Gate};
14///
15/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
16/// // In practice, you would get gates from storage
17/// // let gate1 = /* ... */;
18/// // let gate2 = /* ... */;
19/// // let gate3 = /* ... */;
20/// // let gates = vec![&gate1, &gate2, &gate3];
21/// // let filtered: Vec<&Gate> = GateQuery::new(gates.iter().copied())
22/// //     .by_parameters("FSC-A", "SSC-A")
23/// //     .by_scope(Some("file-guid"))
24/// //     .collect();
25/// # Ok(())
26/// # }
27/// ```
28pub struct GateQuery<'a> {
29    gates: Vec<&'a Gate>,
30}
31
32impl<'a> GateQuery<'a> {
33    /// Create a new query builder from an iterator of gates
34    ///
35    /// # Arguments
36    /// * `gates` - Iterator over gate references
37    pub fn new(gates: impl Iterator<Item = &'a Gate>) -> Self {
38        Self {
39            gates: gates.collect(),
40        }
41    }
42
43    /// Filter gates by parameters (channels)
44    ///
45    /// Only gates that operate on the specified x and y parameters are retained.
46    ///
47    /// # Arguments
48    /// * `x` - X-axis parameter name
49    /// * `y` - Y-axis parameter name
50    pub fn by_parameters(mut self, x: &str, y: &str) -> Self {
51        self.gates.retain(|gate| {
52            gate.x_parameter_channel_name() == x && gate.y_parameter_channel_name() == y
53        });
54        self
55    }
56
57    /// Filter gates by scope (file GUID)
58    ///
59    /// Only gates that apply to the specified file (or are global) are retained.
60    ///
61    /// # Arguments
62    /// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
63    pub fn by_scope(mut self, file_guid: Option<&str>) -> Self {
64        match file_guid {
65            Some(guid) => self.gates.retain(|gate| gate.mode.applies_to(guid)),
66            None => self.gates.retain(|gate| matches!(gate.mode, GateMode::Global)),
67        }
68        self
69    }
70
71    /// Collect the filtered gates into a vector
72    ///
73    /// # Returns
74    /// A vector of gate references matching the query criteria
75    pub fn collect(self) -> Vec<&'a Gate> {
76        self.gates
77    }
78}
79
80impl<'a> IntoIterator for GateQuery<'a> {
81    type Item = &'a Gate;
82    type IntoIter = std::vec::IntoIter<&'a Gate>;
83
84    fn into_iter(self) -> Self::IntoIter {
85        self.gates.into_iter()
86    }
87}
88
89/// Helper functions for managing gate types
90impl GateMode {
91    /// Create a global type
92    pub fn global() -> Self {
93        GateMode::Global
94    }
95
96    /// Create a file-specific type
97    pub fn file_specific(guid: impl Into<Arc<str>>) -> Self {
98        GateMode::FileSpecific { guid: guid.into() }
99    }
100
101    /// Create a file group type
102    pub fn file_group(guids: Vec<impl Into<Arc<str>>>) -> Self {
103        GateMode::FileGroup {
104            guids: guids.into_iter().map(|g| g.into()).collect(),
105        }
106    }
107
108    /// Add a file to a group type (no-op for Global or FileSpecific)
109    pub fn add_file(&mut self, guid: impl Into<Arc<str>>) {
110        if let GateMode::FileGroup { guids } = self {
111            let guid = guid.into();
112            if !guids.contains(&guid) {
113                guids.push(guid);
114            }
115        }
116    }
117
118    /// Remove a file from a group type (no-op for Global or FileSpecific)
119    pub fn remove_file(&mut self, guid: &str) {
120        if let GateMode::FileGroup { guids } = self {
121            guids.retain(|g| g.as_ref() != guid);
122        }
123    }
124
125    /// Get the list of file GUIDs this type applies to (None for Global)
126    pub fn file_guids(&self) -> Option<Vec<&str>> {
127        match self {
128            GateMode::Global => None,
129            GateMode::FileSpecific { guid } => Some(vec![guid.as_ref()]),
130            GateMode::FileGroup { guids } => Some(guids.iter().map(|g| g.as_ref()).collect()),
131        }
132    }
133}
134
135/// Filter gates by type (scope)
136///
137/// This is a convenience function that filters gates by their scope.
138/// Consider using `GateQuery` for more complex filtering needs.
139///
140/// # Arguments
141/// * `gates` - Iterator over gate references
142/// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
143///
144/// # Returns
145/// A vector of gates matching the scope criteria
146pub fn filter_gates_by_type<'a>(
147    gates: impl Iterator<Item = &'a Gate>,
148    file_guid: Option<&str>,
149) -> Vec<&'a Gate> {
150    match file_guid {
151        Some(guid) => gates.filter(|gate| gate.mode.applies_to(guid)).collect(),
152        None => gates
153            .filter(|gate| matches!(gate.mode, GateMode::Global))
154            .collect(),
155    }
156}
157
158/// Filter gates by parameters (channels)
159///
160/// Returns only gates that operate on the specified x and y parameters.
161///
162/// # Arguments
163/// * `gates` - Iterator over gate references
164/// * `x_param` - X-axis parameter name
165/// * `y_param` - Y-axis parameter name
166///
167/// # Returns
168/// A vector of gates operating on the specified parameters
169///
170/// # Example
171/// ```rust,no_run
172/// use flow_gates::{filter_gates_by_parameters, Gate};
173///
174/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
175/// // In practice, you would get gates from storage
176/// // let gate1 = /* ... */;
177/// // let gate2 = /* ... */;
178/// // let gate3 = /* ... */;
179/// // let gates = vec![&gate1, &gate2, &gate3];
180/// // let fsc_ssc_gates = filter_gates_by_parameters(gates.iter().copied(), "FSC-A", "SSC-A");
181/// # Ok(())
182/// # }
183/// ```
184pub fn filter_gates_by_parameters<'a>(
185    gates: impl Iterator<Item = &'a Gate>,
186    x_param: &str,
187    y_param: &str,
188) -> Vec<&'a Gate> {
189    gates
190        .filter(|gate| {
191            gate.x_parameter_channel_name() == x_param && gate.y_parameter_channel_name() == y_param
192        })
193        .collect()
194}
195
196/// Filter gates by scope
197///
198/// Returns gates that apply to the specified file (or are global if `file_guid` is `None`).
199///
200/// # Arguments
201/// * `gates` - Iterator over gate references
202/// * `file_guid` - Optional file GUID. If `None`, only global gates are returned.
203///
204/// # Returns
205/// A vector of gates matching the scope criteria
206///
207/// # Example
208/// ```rust,no_run
209/// use flow_gates::{filter_gates_by_scope, Gate};
210///
211/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
212/// // In practice, you would get gates from storage
213/// // let gate1 = /* ... */;
214/// // let gate2 = /* ... */;
215/// // let gate3 = /* ... */;
216/// // let gates = vec![&gate1, &gate2, &gate3];
217/// // let file_gates = filter_gates_by_scope(gates.iter().copied(), Some("file-123"));
218/// // let global_gates = filter_gates_by_scope(gates.iter().copied(), None);
219/// # Ok(())
220/// # }
221/// ```
222pub fn filter_gates_by_scope<'a>(
223    gates: impl Iterator<Item = &'a Gate>,
224    file_guid: Option<&str>,
225) -> Vec<&'a Gate> {
226    filter_gates_by_type(gates, file_guid)
227}
228
229/// Filter a hierarchy by parameters, rebuilding relationships
230///
231/// Creates a new hierarchy containing only gates that operate on the specified
232/// parameters, preserving parent-child relationships where both gates match.
233///
234/// # Arguments
235/// * `hierarchy` - The original hierarchy
236/// * `gates_map` - Map of gate IDs to gate references
237/// * `x_param` - X-axis parameter name
238/// * `y_param` - Y-axis parameter name
239///
240/// # Returns
241/// A new hierarchy containing only gates matching the parameters
242///
243/// # Example
244/// ```rust,no_run
245/// use flow_gates::{filter_hierarchy_by_parameters, GateHierarchy, Gate};
246/// use std::collections::HashMap;
247/// use std::sync::Arc;
248///
249/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
250/// let hierarchy = GateHierarchy::new();
251/// let mut gates_map: HashMap<Arc<str>, &Gate> = HashMap::new();
252/// // ... populate gates_map ...
253///
254/// let filtered = filter_hierarchy_by_parameters(&hierarchy, &gates_map, "FSC-A", "SSC-A");
255/// # Ok(())
256/// # }
257/// ```
258pub fn filter_hierarchy_by_parameters(
259    hierarchy: &GateHierarchy,
260    gates_map: &std::collections::HashMap<Arc<str>, &Gate>,
261    x_param: &str,
262    y_param: &str,
263) -> GateHierarchy {
264    let mut new_hierarchy = GateHierarchy::new();
265
266    // Get all gates matching parameters
267    let matching_gates: std::collections::HashSet<Arc<str>> = gates_map
268        .iter()
269        .filter(|(_, gate)| {
270            gate.x_parameter_channel_name() == x_param
271                && gate.y_parameter_channel_name() == y_param
272        })
273        .map(|(id, _)| id.clone())
274        .collect();
275
276    // Rebuild relationships for matching gates using public API
277    // Get all gates in hierarchy and check their relationships
278    if let Some(sorted_gates) = hierarchy.topological_sort() {
279        for gate_id in sorted_gates {
280            if matching_gates.contains(&gate_id) {
281                // Check if this gate has a parent that also matches
282                if let Some(parent_id) = hierarchy.get_parent(gate_id.as_ref()) {
283                    if matching_gates.contains(parent_id) {
284                        let _ = new_hierarchy.add_child(parent_id.clone(), gate_id.clone());
285                    }
286                }
287            }
288        }
289    }
290
291    new_hierarchy
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    #[test]
299    fn test_global_type_applies_to_all() {
300        let gate_type = GateMode::global();
301        assert!(gate_type.applies_to("any-guid"));
302        assert!(gate_type.applies_to("another-guid"));
303    }
304
305    #[test]
306    fn test_file_specific_type() {
307        let gate_type = GateMode::file_specific("test-guid");
308        assert!(gate_type.applies_to("test-guid"));
309        assert!(!gate_type.applies_to("other-guid"));
310    }
311
312    #[test]
313    fn test_file_group_type() {
314        let mut gate_type = GateMode::file_group(vec!["guid1", "guid2"]);
315        assert!(gate_type.applies_to("guid1"));
316        assert!(gate_type.applies_to("guid2"));
317        assert!(!gate_type.applies_to("guid3"));
318
319        gate_type.add_file("guid3");
320        assert!(gate_type.applies_to("guid3"));
321
322        gate_type.remove_file("guid1");
323        assert!(!gate_type.applies_to("guid1"));
324    }
325}