flow_gates/
types.rs

1use crate::error::{GateError, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::sync::Arc;
5
6/// A node in a gate, representing a control point with coordinates in raw data space.
7///
8/// Gate nodes store coordinates for multiple channels, allowing gates to be defined
9/// in multi-dimensional space. Coordinates are stored as `f32` values in raw data units.
10///
11/// # Example
12///
13/// ```rust
14/// use flow_gates::GateNode;
15///
16/// let node = GateNode::new("node1")
17///     .with_coordinate("FSC-A", 1000.0)
18///     .with_coordinate("SSC-A", 2000.0);
19///
20/// assert_eq!(node.get_coordinate("FSC-A"), Some(1000.0));
21/// ```
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct GateNode {
24    /// Unique identifier for this node
25    pub id: Arc<str>,
26    /// Coordinates in raw data space, keyed by channel name.
27    ///
28    /// Using `Arc<str>` for channel names reduces allocations since they're shared
29    /// across multiple nodes and gates.
30    #[serde(with = "arc_str_hashmap")]
31    pub coordinates: HashMap<Arc<str>, f32>,
32}
33
34impl GateNode {
35    /// Create a new gate node with the given ID.
36    ///
37    /// The node starts with no coordinates. Use `with_coordinate` or `set_coordinate`
38    /// to add coordinate values.
39    pub fn new(id: impl Into<Arc<str>>) -> Self {
40        Self {
41            id: id.into(),
42            coordinates: HashMap::new(),
43        }
44    }
45
46    /// Add a coordinate value using the builder pattern.
47    ///
48    /// Returns `self` for method chaining.
49    pub fn with_coordinate(mut self, channel: impl Into<Arc<str>>, value: f32) -> Self {
50        self.coordinates.insert(channel.into(), value);
51        self
52    }
53
54    /// Get a coordinate value for the specified channel.
55    ///
56    /// Returns `None` if the channel is not present in this node.
57    pub fn get_coordinate(&self, channel: &str) -> Option<f32> {
58        self.coordinates.get(channel).copied()
59    }
60
61    pub fn set_coordinate(&mut self, channel: impl Into<Arc<str>>, value: f32) {
62        self.coordinates.insert(channel.into(), value);
63    }
64}
65
66/// Boolean operation for combining gates
67///
68/// Boolean gates combine multiple gates using logical operations:
69/// - **And**: Events must pass all operand gates
70/// - **Or**: Events must pass at least one operand gate
71/// - **Not**: Events must NOT pass the operand gate (complement)
72///
73/// # Example
74///
75/// ```rust
76/// use flow_gates::BooleanOperation;
77///
78/// let and_op = BooleanOperation::And;
79/// let or_op = BooleanOperation::Or;
80/// let not_op = BooleanOperation::Not;
81/// ```
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "lowercase")]
84pub enum BooleanOperation {
85    /// AND operation - events must pass all operand gates
86    And,
87    /// OR operation - events must pass at least one operand gate
88    Or,
89    /// NOT operation - events must NOT pass the operand gate (single operand)
90    Not,
91}
92
93impl BooleanOperation {
94    /// Get the expected number of operands for this operation
95    ///
96    /// # Returns
97    /// - `And`: `None` (any number >= 2)
98    /// - `Or`: `None` (any number >= 2)
99    /// - `Not`: `Some(1)` (exactly one operand)
100    pub fn expected_operand_count(&self) -> Option<usize> {
101        match self {
102            BooleanOperation::And | BooleanOperation::Or => None, // At least 2
103            BooleanOperation::Not => Some(1),
104        }
105    }
106
107    /// Get a string representation of the operation
108    pub fn as_str(&self) -> &'static str {
109        match self {
110            BooleanOperation::And => "and",
111            BooleanOperation::Or => "or",
112            BooleanOperation::Not => "not",
113        }
114    }
115}
116
117/// The geometry of a gate, defining its shape in 2D parameter space.
118///
119/// Gates can be one of four geometric types:
120/// - **Polygon**: A closed or open polygonal region defined by vertices
121/// - **Rectangle**: An axis-aligned rectangular region
122/// - **Ellipse**: An elliptical region with optional rotation
123/// - **Boolean**: A combination of other gates using boolean operations (AND, OR, NOT)
124///
125/// All geometries operate in raw data coordinate space and are parameterized
126/// by two channel names (x and y parameters).
127///
128/// # Example
129///
130/// ```rust
131/// use flow_gates::{GateGeometry, GateNode};
132///
133/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
134/// // Create a rectangle gate
135/// let min = GateNode::new("min")
136///     .with_coordinate("FSC-A", 100.0)
137///     .with_coordinate("SSC-A", 200.0);
138/// let max = GateNode::new("max")
139///     .with_coordinate("FSC-A", 500.0)
140///     .with_coordinate("SSC-A", 600.0);
141///
142/// let geometry = GateGeometry::Rectangle { min, max };
143///
144/// // Check if a point is inside
145/// let inside = geometry.contains_point(300.0, 400.0, "FSC-A", "SSC-A")?;
146/// # Ok(())
147/// # }
148/// ```
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(tag = "type")]
151pub enum GateGeometry {
152    Polygon {
153        nodes: Vec<GateNode>,
154        closed: bool,
155    },
156    Rectangle {
157        min: GateNode,
158        max: GateNode,
159    },
160    Ellipse {
161        center: GateNode,
162        radius_x: f32,
163        radius_y: f32,
164        angle: f32, // rotation angle in radians
165    },
166    /// Boolean gate combining other gates with logical operations
167    ///
168    /// Boolean gates reference other gates by ID and combine their results
169    /// using AND, OR, or NOT operations. The referenced gates must be resolved
170    /// externally when filtering events.
171    Boolean {
172        /// The boolean operation to apply
173        operation: BooleanOperation,
174        /// IDs of the gates to combine (gate IDs, not the gates themselves)
175        operands: Vec<Arc<str>>,
176    },
177}
178
179impl GateGeometry {
180    /// Calculate the bounding box for this geometry in the specified parameter space
181    pub fn bounding_box(&self, x_param: &str, y_param: &str) -> Option<(f32, f32, f32, f32)> {
182        match self {
183            GateGeometry::Polygon { nodes, .. } => {
184                let mut min_x = f32::MAX;
185                let mut min_y = f32::MAX;
186                let mut max_x = f32::MIN;
187                let mut max_y = f32::MIN;
188
189                for node in nodes {
190                    if let (Some(x), Some(y)) =
191                        (node.get_coordinate(x_param), node.get_coordinate(y_param))
192                    {
193                        min_x = min_x.min(x);
194                        min_y = min_y.min(y);
195                        max_x = max_x.max(x);
196                        max_y = max_y.max(y);
197                    }
198                }
199
200                if min_x < max_x && min_y < max_y {
201                    Some((min_x, min_y, max_x, max_y))
202                } else {
203                    None
204                }
205            }
206            GateGeometry::Rectangle { min, max } => {
207                if let (Some(min_x), Some(min_y), Some(max_x), Some(max_y)) = (
208                    min.get_coordinate(x_param),
209                    min.get_coordinate(y_param),
210                    max.get_coordinate(x_param),
211                    max.get_coordinate(y_param),
212                ) {
213                    Some((min_x, min_y, max_x, max_y))
214                } else {
215                    None
216                }
217            }
218            GateGeometry::Ellipse {
219                center,
220                radius_x,
221                radius_y,
222                angle,
223            } => {
224                if let (Some(cx), Some(cy)) = (
225                    center.get_coordinate(x_param),
226                    center.get_coordinate(y_param),
227                ) {
228                    let cos_a = angle.cos();
229                    let sin_a = angle.sin();
230
231                    // Maximum extents along each axis after rotation
232                    let extent_x = ((radius_x * cos_a).powi(2) + (radius_y * sin_a).powi(2)).sqrt();
233                    let extent_y = ((radius_x * sin_a).powi(2) + (radius_y * cos_a).powi(2)).sqrt();
234
235                    Some((cx - extent_x, cy - extent_y, cx + extent_x, cy + extent_y))
236                } else {
237                    None
238                }
239            }
240            GateGeometry::Boolean { .. } => {
241                // Boolean gates don't have a direct bounding box - would need to resolve operands
242                // For now, return None to indicate it can't be calculated directly
243                None
244            }
245        }
246    }
247
248    /// Calculate the center point in raw data coordinates
249    pub fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
250        match self {
251            GateGeometry::Polygon { nodes, .. } => {
252                let (sum_x, sum_y, count) = nodes
253                    .iter()
254                    .filter_map(|node| {
255                        let x = node.get_coordinate(x_param)?;
256                        let y = node.get_coordinate(y_param)?;
257                        Some((x, y))
258                    })
259                    .fold((0.0, 0.0, 0), |(sx, sy, c), (x, y)| (sx + x, sy + y, c + 1));
260
261                if count > 0 {
262                    Ok((sum_x / count as f32, sum_y / count as f32))
263                } else {
264                    Err(GateError::invalid_geometry(
265                        "Polygon has no valid coordinates",
266                    ))
267                }
268            }
269            GateGeometry::Rectangle { min, max } => {
270                let min_x = min
271                    .get_coordinate(x_param)
272                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
273                let min_y = min
274                    .get_coordinate(y_param)
275                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
276                let max_x = max
277                    .get_coordinate(x_param)
278                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
279                let max_y = max
280                    .get_coordinate(y_param)
281                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
282
283                Ok(((min_x + max_x) / 2.0, (min_y + max_y) / 2.0))
284            }
285            GateGeometry::Ellipse { center, .. } => {
286                let cx = center
287                    .get_coordinate(x_param)
288                    .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
289                let cy = center
290                    .get_coordinate(y_param)
291                    .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
292
293                Ok((cx, cy))
294            }
295            GateGeometry::Boolean { .. } => {
296                // Boolean gates don't have a direct center - would need to resolve operands
297                Err(GateError::invalid_geometry(
298                    "Boolean gates do not have a direct center point",
299                ))
300            }
301        }
302    }
303
304    /// Check if a point (in raw coordinates) is inside the gate
305    pub fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
306        match self {
307            GateGeometry::Polygon { nodes, closed } => {
308                if !closed {
309                    return Ok(false);
310                }
311
312                // Extract coordinates
313                let coords: Vec<(f32, f32)> = nodes
314                    .iter()
315                    .filter_map(|node| {
316                        Some((node.get_coordinate(x_param)?, node.get_coordinate(y_param)?))
317                    })
318                    .collect();
319
320                if coords.len() < 3 {
321                    return Ok(false);
322                }
323
324                // Ray casting algorithm
325                Ok(point_in_polygon(x, y, &coords))
326            }
327            GateGeometry::Rectangle { min, max } => {
328                let min_x = min
329                    .get_coordinate(x_param)
330                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
331                let min_y = min
332                    .get_coordinate(y_param)
333                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
334                let max_x = max
335                    .get_coordinate(x_param)
336                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
337                let max_y = max
338                    .get_coordinate(y_param)
339                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
340
341                Ok(x >= min_x && x <= max_x && y >= min_y && y <= max_y)
342            }
343            GateGeometry::Ellipse {
344                center,
345                radius_x,
346                radius_y,
347                angle,
348            } => {
349                let cx = center
350                    .get_coordinate(x_param)
351                    .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
352                let cy = center
353                    .get_coordinate(y_param)
354                    .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
355
356                // Rotate point around center by -angle
357                let cos_a = angle.cos();
358                let sin_a = angle.sin();
359                let dx = x - cx;
360                let dy = y - cy;
361                let rotated_x = dx * cos_a + dy * sin_a;
362                let rotated_y = -dx * sin_a + dy * cos_a;
363
364                // Check if point is inside axis-aligned ellipse
365                let normalized = (rotated_x / radius_x).powi(2) + (rotated_y / radius_y).powi(2);
366                Ok(normalized <= 1.0)
367            }
368            GateGeometry::Boolean { .. } => {
369                // Boolean gates require resolving referenced gates - can't check containment directly
370                // This should be handled by the filtering functions that resolve gate references
371                Err(GateError::invalid_geometry(
372                    "Boolean gates require gate resolution to check containment",
373                ))
374            }
375        }
376    }
377
378    /// Batch check if points (in raw coordinates) are inside the gate
379    ///
380    /// Uses optimized CPU-based batch filtering with Rayon parallelization.
381    pub fn contains_points_batch(
382        &self,
383        points: &[(f32, f32)],
384        x_param: &str,
385        y_param: &str,
386    ) -> Result<Vec<bool>> {
387        match self {
388            GateGeometry::Polygon { nodes, closed } => {
389                if !closed {
390                    return Ok(vec![false; points.len()]);
391                }
392
393                // Extract coordinates
394                let coords: Vec<(f32, f32)> = nodes
395                    .iter()
396                    .filter_map(|node| {
397                        Some((node.get_coordinate(x_param)?, node.get_coordinate(y_param)?))
398                    })
399                    .collect();
400
401                if coords.len() < 3 {
402                    return Ok(vec![false; points.len()]);
403                }
404
405                crate::batch_filtering::filter_by_polygon_batch(points, &coords)
406            }
407            GateGeometry::Rectangle { min, max } => {
408                let min_x = min
409                    .get_coordinate(x_param)
410                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
411                let min_y = min
412                    .get_coordinate(y_param)
413                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
414                let max_x = max
415                    .get_coordinate(x_param)
416                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
417                let max_y = max
418                    .get_coordinate(y_param)
419                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
420
421                crate::batch_filtering::filter_by_rectangle_batch(
422                    points,
423                    (min_x, min_y, max_x, max_y),
424                )
425            }
426            GateGeometry::Ellipse {
427                center,
428                radius_x,
429                radius_y,
430                angle,
431            } => {
432                let cx = center
433                    .get_coordinate(x_param)
434                    .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
435                let cy = center
436                    .get_coordinate(y_param)
437                    .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
438
439                crate::batch_filtering::filter_by_ellipse_batch(
440                    points,
441                    (cx, cy),
442                    *radius_x,
443                    *radius_y,
444                    *angle,
445                )
446            }
447            GateGeometry::Boolean { .. } => {
448                // Boolean gates require resolving referenced gates - can't check containment directly
449                Err(GateError::invalid_geometry(
450                    "Boolean gates require gate resolution to check containment",
451                ))
452            }
453        }
454    }
455
456    /// Check if the gate has valid geometry and coordinates
457    pub fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
458        match self {
459            GateGeometry::Polygon { nodes, .. } => {
460                // Need at least 3 nodes for a valid polygon
461                if nodes.len() < 3 {
462                    return Ok(false);
463                }
464
465                // All nodes must have valid coordinates
466                let valid_coords = nodes.iter().all(|node| {
467                    node.get_coordinate(x_param).is_some() && node.get_coordinate(y_param).is_some()
468                });
469
470                Ok(valid_coords)
471            }
472            GateGeometry::Rectangle { min, max } => {
473                // Must have valid coordinates
474                if min.get_coordinate(x_param).is_none()
475                    || min.get_coordinate(y_param).is_none()
476                    || max.get_coordinate(x_param).is_none()
477                    || max.get_coordinate(y_param).is_none()
478                {
479                    return Ok(false);
480                }
481
482                // Min must be less than max
483                let min_x = min.get_coordinate(x_param).unwrap();
484                let min_y = min.get_coordinate(y_param).unwrap();
485                let max_x = max.get_coordinate(x_param).unwrap();
486                let max_y = max.get_coordinate(y_param).unwrap();
487
488                Ok(min_x < max_x && min_y < max_y)
489            }
490            GateGeometry::Ellipse {
491                center,
492                radius_x,
493                radius_y,
494                ..
495            } => {
496                // Must have valid center coordinates
497                if center.get_coordinate(x_param).is_none()
498                    || center.get_coordinate(y_param).is_none()
499                {
500                    return Ok(false);
501                }
502
503                // Radii must be positive
504                Ok(radius_x > &0.0 && radius_y > &0.0)
505            }
506            GateGeometry::Boolean {
507                operation,
508                operands,
509            } => {
510                // Validate operand count
511                match operation {
512                    BooleanOperation::And | BooleanOperation::Or => {
513                        if operands.len() < 2 {
514                            return Ok(false);
515                        }
516                    }
517                    BooleanOperation::Not => {
518                        if operands.len() != 1 {
519                            return Ok(false);
520                        }
521                    }
522                }
523                Ok(true)
524            }
525        }
526    }
527
528    /// Get a descriptive name for this gate type
529    pub fn gate_type_name(&self) -> &'static str {
530        match self {
531            GateGeometry::Polygon { .. } => "Polygon",
532            GateGeometry::Rectangle { .. } => "Rectangle",
533            GateGeometry::Ellipse { .. } => "Ellipse",
534            GateGeometry::Boolean { .. } => "Boolean",
535        }
536    }
537}
538
539// Implement geometry traits for GateGeometry enum by delegating to struct implementations
540use crate::traits::*;
541
542impl GateCenter for GateGeometry {
543    fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
544        match self {
545            GateGeometry::Polygon { nodes, closed } => {
546                let poly = crate::polygon::PolygonGateGeometry {
547                    nodes: nodes.clone(),
548                    closed: *closed,
549                };
550                poly.calculate_center(x_param, y_param)
551            }
552            GateGeometry::Rectangle { min, max } => {
553                let rect = crate::rectangle::RectangleGateGeometry {
554                    min: min.clone(),
555                    max: max.clone(),
556                };
557                rect.calculate_center(x_param, y_param)
558            }
559            GateGeometry::Ellipse {
560                center,
561                radius_x,
562                radius_y,
563                angle,
564            } => {
565                let ellipse = crate::ellipse::EllipseGateGeometry {
566                    center: center.clone(),
567                    radius_x: *radius_x,
568                    radius_y: *radius_y,
569                    angle: *angle,
570                };
571                ellipse.calculate_center(x_param, y_param)
572            }
573            GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
574                "Boolean gates do not have a direct center point",
575            )),
576        }
577    }
578}
579
580impl GateContainment for GateGeometry {
581    fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
582        match self {
583            GateGeometry::Polygon { nodes, closed } => {
584                let poly = crate::polygon::PolygonGateGeometry {
585                    nodes: nodes.clone(),
586                    closed: *closed,
587                };
588                poly.contains_point(x, y, x_param, y_param)
589            }
590            GateGeometry::Rectangle { min, max } => {
591                let rect = crate::rectangle::RectangleGateGeometry {
592                    min: min.clone(),
593                    max: max.clone(),
594                };
595                rect.contains_point(x, y, x_param, y_param)
596            }
597            GateGeometry::Ellipse {
598                center,
599                radius_x,
600                radius_y,
601                angle,
602            } => {
603                let ellipse = crate::ellipse::EllipseGateGeometry {
604                    center: center.clone(),
605                    radius_x: *radius_x,
606                    radius_y: *radius_y,
607                    angle: *angle,
608                };
609                ellipse.contains_point(x, y, x_param, y_param)
610            }
611            GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
612                "Boolean gates require gate resolution to check containment",
613            )),
614        }
615    }
616}
617
618impl GateBounds for GateGeometry {
619    fn bounding_box(&self, x_param: &str, y_param: &str) -> Result<(f32, f32, f32, f32)> {
620        match self {
621            GateGeometry::Polygon { nodes, closed } => {
622                let poly = crate::polygon::PolygonGateGeometry {
623                    nodes: nodes.clone(),
624                    closed: *closed,
625                };
626                poly.bounding_box(x_param, y_param)
627            }
628            GateGeometry::Rectangle { min, max } => {
629                let rect = crate::rectangle::RectangleGateGeometry {
630                    min: min.clone(),
631                    max: max.clone(),
632                };
633                rect.bounding_box(x_param, y_param)
634            }
635            GateGeometry::Ellipse {
636                center,
637                radius_x,
638                radius_y,
639                angle,
640            } => {
641                let ellipse = crate::ellipse::EllipseGateGeometry {
642                    center: center.clone(),
643                    radius_x: *radius_x,
644                    radius_y: *radius_y,
645                    angle: *angle,
646                };
647                ellipse.bounding_box(x_param, y_param)
648            }
649            GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
650                "Boolean gates do not have a direct bounding box",
651            )),
652        }
653    }
654}
655
656impl GateValidation for GateGeometry {
657    fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
658        match self {
659            GateGeometry::Polygon { nodes, closed } => {
660                let poly = crate::polygon::PolygonGateGeometry {
661                    nodes: nodes.clone(),
662                    closed: *closed,
663                };
664                poly.is_valid(x_param, y_param)
665            }
666            GateGeometry::Rectangle { min, max } => {
667                let rect = crate::rectangle::RectangleGateGeometry {
668                    min: min.clone(),
669                    max: max.clone(),
670                };
671                rect.is_valid(x_param, y_param)
672            }
673            GateGeometry::Ellipse {
674                center,
675                radius_x,
676                radius_y,
677                angle,
678            } => {
679                let ellipse = crate::ellipse::EllipseGateGeometry {
680                    center: center.clone(),
681                    radius_x: *radius_x,
682                    radius_y: *radius_y,
683                    angle: *angle,
684                };
685                ellipse.is_valid(x_param, y_param)
686            }
687            GateGeometry::Boolean {
688                operation,
689                operands,
690            } => {
691                match operation {
692                    BooleanOperation::And | BooleanOperation::Or => {
693                        if operands.len() < 2 {
694                            return Err(GateError::invalid_boolean_operation(
695                                operation.as_str(),
696                                operands.len(),
697                                2,
698                            ));
699                        }
700                    }
701                    BooleanOperation::Not => {
702                        if operands.len() != 1 {
703                            return Err(GateError::invalid_boolean_operation(
704                                operation.as_str(),
705                                operands.len(),
706                                1,
707                            ));
708                        }
709                    }
710                }
711                Ok(true)
712            }
713        }
714    }
715}
716
717impl GateGeometryOps for GateGeometry {
718    fn gate_type_name(&self) -> &'static str {
719        match self {
720            GateGeometry::Polygon { .. } => "Polygon",
721            GateGeometry::Rectangle { .. } => "Rectangle",
722            GateGeometry::Ellipse { .. } => "Ellipse",
723            GateGeometry::Boolean { .. } => "Boolean",
724        }
725    }
726}
727
728/// Point-in-polygon using ray casting algorithm
729fn point_in_polygon(x: f32, y: f32, polygon: &[(f32, f32)]) -> bool {
730    let mut inside = false;
731    let n = polygon.len();
732
733    for i in 0..n {
734        let (x1, y1) = polygon[i];
735        let (x2, y2) = polygon[(i + 1) % n];
736
737        if ((y1 > y) != (y2 > y)) && (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1) {
738            inside = !inside;
739        }
740    }
741
742    inside
743}
744
745/// The scope of a gate - determines which files it applies to.
746///
747/// Gates can be:
748/// - **Global**: Applies to all files
749/// - **FileSpecific**: Applies only to a single file (identified by GUID)
750/// - **FileGroup**: Applies to a specific set of files
751///
752/// This allows gates to be shared across multiple files or restricted to
753/// specific datasets.
754///
755/// # Example
756///
757/// ```rust
758/// use flow_gates::GateMode;
759///
760/// // Global gate (applies to all files)
761/// let global = GateMode::Global;
762/// assert!(global.applies_to("any-file-guid"));
763///
764/// // File-specific gate
765/// let specific = GateMode::FileSpecific { guid: "file-123".into() };
766/// assert!(specific.applies_to("file-123"));
767/// assert!(!specific.applies_to("file-456"));
768///
769/// // File group gate
770/// let group = GateMode::FileGroup { guids: vec!["file-1".into(), "file-2".into()] };
771/// assert!(group.applies_to("file-1"));
772/// assert!(group.applies_to("file-2"));
773/// assert!(!group.applies_to("file-3"));
774/// ```
775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
776#[serde(tag = "name")]
777pub enum GateMode {
778    /// Gate applies to all files
779    Global,
780    /// Gate applies only to a specific file
781    FileSpecific {
782        /// File GUID
783        #[serde(with = "arc_str_serde")]
784        guid: Arc<str>,
785    },
786    /// Gate applies to a group of files
787    FileGroup {
788        /// List of file GUIDs
789        #[serde(with = "arc_str_vec")]
790        guids: Vec<Arc<str>>,
791    },
792}
793
794impl GateMode {
795    /// Check if this gate mode applies to the given file GUID.
796    ///
797    /// Returns `true` if the gate should be applied to the specified file.
798    pub fn applies_to(&self, file_guid: &str) -> bool {
799        match self {
800            GateMode::Global => true,
801            GateMode::FileSpecific { guid } => guid.as_ref() == file_guid,
802            GateMode::FileGroup { guids } => guids.iter().any(|g| g.as_ref() == file_guid),
803        }
804    }
805}
806
807/// Label position stored as offset from the first node in raw data coordinates
808/// This allows labels to move with gates when they are edited
809#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
810pub struct LabelPosition {
811    /// Offset in raw data coordinates from the first node
812    pub offset_x: f32,
813    pub offset_y: f32,
814}
815
816/// A gate represents a region of interest in flow cytometry data.
817///
818/// Gates define 2D regions in parameter space that can be used to filter
819/// and analyze cytometry events. Each gate has:
820///
821/// - A unique identifier
822/// - A human-readable name
823/// - A geometric shape (polygon, rectangle, or ellipse)
824/// - Two parameters (channels) it operates on
825/// - A scope (global, file-specific, or file group)
826///
827/// # Example
828///
829/// ```rust
830/// use flow_gates::{Gate, GateGeometry, GateNode, geometry::*};
831///
832/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
833/// // Create a polygon gate
834/// let coords = vec![
835///     (100.0, 200.0),
836///     (300.0, 200.0),
837///     (300.0, 400.0),
838///     (100.0, 400.0),
839/// ];
840/// let geometry = create_polygon_geometry(coords, "FSC-A", "SSC-A")?;
841///
842/// let gate = Gate::new(
843///     "lymphocytes",
844///     "Lymphocytes",
845///     geometry,
846///     "FSC-A",
847///     "SSC-A",
848/// );
849///
850/// // Get parameter names
851/// assert_eq!(gate.x_parameter_channel_name(), "FSC-A");
852/// assert_eq!(gate.y_parameter_channel_name(), "SSC-A");
853/// # Ok(())
854/// # }
855/// ```
856#[derive(Debug, Clone, Serialize, Deserialize)]
857pub struct Gate {
858    #[serde(with = "arc_str_serde")]
859    pub id: Arc<str>,
860    pub name: String,
861    pub geometry: GateGeometry,
862    pub mode: GateMode,
863    /// The parameters (channels) this gate operates on (x_channel, y_channel)
864    #[serde(with = "arc_str_pair")]
865    pub parameters: (Arc<str>, Arc<str>),
866    /// Optional label position as offset from first node in raw data coordinates
867    pub label_position: Option<LabelPosition>,
868}
869
870impl Gate {
871    /// Create a new gate with the specified properties.
872    ///
873    /// The gate is created with `GateMode::Global` by default. Set the `mode` field
874    /// to make it file-specific or part of a file group.
875    ///
876    /// # Arguments
877    ///
878    /// * `id` - Unique identifier for the gate
879    /// * `name` - Human-readable name for the gate
880    /// * `geometry` - The geometric shape of the gate
881    /// * `x_param` - Channel name for the x-axis parameter
882    /// * `y_param` - Channel name for the y-axis parameter
883    pub fn new(
884        id: impl Into<Arc<str>>,
885        name: impl Into<String>,
886        geometry: GateGeometry,
887        x_param: impl Into<Arc<str>>,
888        y_param: impl Into<Arc<str>>,
889    ) -> Self {
890        let x_param = x_param.into();
891        let y_param = y_param.into();
892
893        Self {
894            id: id.into(),
895            name: name.into(),
896            geometry,
897            mode: GateMode::Global, // Default to global
898            parameters: (x_param, y_param),
899            label_position: None,
900        }
901    }
902
903    /// Get the x parameter (channel name)
904    pub fn x_parameter_channel_name(&self) -> &str {
905        self.parameters.0.as_ref()
906    }
907
908    /// Get the y parameter (channel name)
909    pub fn y_parameter_channel_name(&self) -> &str {
910        self.parameters.1.as_ref()
911    }
912
913    /// Check if a point (in gate's parameter space) is inside the gate
914    ///
915    /// This is a convenience method that uses the gate's own parameters,
916    /// so you don't need to specify them explicitly.
917    ///
918    /// # Arguments
919    /// * `x` - X coordinate in raw data space
920    /// * `y` - Y coordinate in raw data space
921    ///
922    /// # Returns
923    /// `true` if the point is inside the gate, `false` otherwise
924    ///
925    /// # Errors
926    /// Returns an error if the gate geometry is invalid or parameters are missing
927    ///
928    /// # Example
929    /// ```rust
930    /// use flow_gates::Gate;
931    ///
932    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
933    /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
934    /// assert!(gate.contains_point(300.0, 400.0)?);
935    /// assert!(!gate.contains_point(50.0, 50.0)?);
936    /// # Ok(())
937    /// # }
938    /// ```
939    pub fn contains_point(&self, x: f32, y: f32) -> Result<bool> {
940        self.geometry.contains_point(
941            x,
942            y,
943            self.x_parameter_channel_name(),
944            self.y_parameter_channel_name(),
945        )
946    }
947
948    /// Get the bounding box in gate's parameter space
949    ///
950    /// This is a convenience method that uses the gate's own parameters.
951    ///
952    /// # Returns
953    /// `Some((min_x, min_y, max_x, max_y))` if the bounding box can be calculated,
954    /// `None` otherwise
955    ///
956    /// # Example
957    /// ```rust
958    /// use flow_gates::Gate;
959    ///
960    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
961    /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
962    /// let bbox = gate.bounding_box();
963    /// assert_eq!(bbox, Some((100.0, 200.0, 500.0, 600.0)));
964    /// # Ok(())
965    /// # }
966    /// ```
967    pub fn bounding_box(&self) -> Option<(f32, f32, f32, f32)> {
968        self.geometry.bounding_box(
969            self.x_parameter_channel_name(),
970            self.y_parameter_channel_name(),
971        )
972    }
973
974    /// Get x and y coordinates from a node for this gate's parameters
975    ///
976    /// This is a convenience method that extracts coordinates for the gate's
977    /// x and y parameters from a node.
978    ///
979    /// # Arguments
980    /// * `node` - The gate node to extract coordinates from
981    ///
982    /// # Returns
983    /// `Some((x, y))` if both coordinates are present, `None` otherwise
984    ///
985    /// # Example
986    /// ```rust
987    /// use flow_gates::{Gate, GateNode};
988    ///
989    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
990    /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
991    /// let node = GateNode::new("node1")
992    ///     .with_coordinate("FSC-A", 300.0)
993    ///     .with_coordinate("SSC-A", 400.0);
994    /// let coords = gate.get_node_coords(&node);
995    /// assert_eq!(coords, Some((300.0, 400.0)));
996    /// # Ok(())
997    /// # }
998    /// ```
999    pub fn get_node_coords(&self, node: &GateNode) -> Option<(f32, f32)> {
1000        Some((
1001            node.get_coordinate(self.x_parameter_channel_name())?,
1002            node.get_coordinate(self.y_parameter_channel_name())?,
1003        ))
1004    }
1005
1006    /// Clone this gate with a new ID
1007    ///
1008    /// Creates a new gate with the same properties but a different ID.
1009    /// Useful for duplicating gates or creating variations.
1010    ///
1011    /// # Arguments
1012    /// * `new_id` - The new ID for the cloned gate
1013    ///
1014    /// # Returns
1015    /// A new `Gate` instance with the specified ID
1016    ///
1017    /// # Example
1018    /// ```rust
1019    /// use flow_gates::Gate;
1020    ///
1021    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1022    /// let gate1 = Gate::rectangle("gate1", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
1023    /// let gate2 = gate1.clone_with_id("gate2");
1024    /// assert_eq!(gate2.id.as_ref(), "gate2");
1025    /// assert_eq!(gate1.name, gate2.name);
1026    /// # Ok(())
1027    /// # }
1028    /// ```
1029    pub fn clone_with_id(&self, new_id: impl Into<Arc<str>>) -> Self {
1030        Self {
1031            id: new_id.into(),
1032            name: self.name.clone(),
1033            geometry: self.geometry.clone(),
1034            mode: self.mode.clone(),
1035            parameters: self.parameters.clone(),
1036            label_position: self.label_position.clone(),
1037        }
1038    }
1039
1040    /// Create a polygon gate from coordinates
1041    ///
1042    /// Convenience constructor for creating polygon gates directly.
1043    ///
1044    /// # Arguments
1045    /// * `id` - Unique identifier for the gate
1046    /// * `name` - Human-readable name for the gate
1047    /// * `coords` - Vector of (x, y) coordinate tuples
1048    /// * `x_param` - Channel name for the x-axis parameter
1049    /// * `y_param` - Channel name for the y-axis parameter
1050    ///
1051    /// # Returns
1052    /// A new `Gate` with polygon geometry
1053    ///
1054    /// # Errors
1055    /// Returns an error if the coordinates are invalid (less than 3 points, non-finite values)
1056    ///
1057    /// # Example
1058    /// ```rust
1059    /// use flow_gates::Gate;
1060    ///
1061    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1062    /// let coords = vec![
1063    ///     (100.0, 200.0),
1064    ///     (300.0, 200.0),
1065    ///     (300.0, 400.0),
1066    ///     (100.0, 400.0),
1067    /// ];
1068    /// let gate = Gate::polygon("poly", "Polygon", coords, "FSC-A", "SSC-A")?;
1069    /// # Ok(())
1070    /// # }
1071    /// ```
1072    pub fn polygon(
1073        id: impl Into<Arc<str>>,
1074        name: impl Into<String>,
1075        coords: Vec<(f32, f32)>,
1076        x_param: impl Into<Arc<str>>,
1077        y_param: impl Into<Arc<str>>,
1078    ) -> Result<Self> {
1079        use crate::geometry::create_polygon_geometry;
1080        let x_param_arc = x_param.into();
1081        let y_param_arc = y_param.into();
1082        let geometry = create_polygon_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1083        Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1084    }
1085
1086    /// Create a rectangle gate from min and max coordinates
1087    ///
1088    /// Convenience constructor for creating rectangle gates directly.
1089    ///
1090    /// # Arguments
1091    /// * `id` - Unique identifier for the gate
1092    /// * `name` - Human-readable name for the gate
1093    /// * `min` - (x, y) coordinates for the minimum corner
1094    /// * `max` - (x, y) coordinates for the maximum corner
1095    /// * `x_param` - Channel name for the x-axis parameter
1096    /// * `y_param` - Channel name for the y-axis parameter
1097    ///
1098    /// # Returns
1099    /// A new `Gate` with rectangle geometry
1100    ///
1101    /// # Errors
1102    /// Returns an error if the coordinates are invalid (min > max, non-finite values)
1103    ///
1104    /// # Example
1105    /// ```rust
1106    /// use flow_gates::Gate;
1107    ///
1108    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1109    /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
1110    /// # Ok(())
1111    /// # }
1112    /// ```
1113    pub fn rectangle(
1114        id: impl Into<Arc<str>>,
1115        name: impl Into<String>,
1116        min: (f32, f32),
1117        max: (f32, f32),
1118        x_param: impl Into<Arc<str>>,
1119        y_param: impl Into<Arc<str>>,
1120    ) -> Result<Self> {
1121        use crate::geometry::create_rectangle_geometry;
1122        let x_param_arc = x_param.into();
1123        let y_param_arc = y_param.into();
1124        let coords = vec![min, max];
1125        let geometry =
1126            create_rectangle_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1127        Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1128    }
1129
1130    /// Create an ellipse gate from center, radii, and angle
1131    ///
1132    /// Convenience constructor for creating ellipse gates directly.
1133    ///
1134    /// # Arguments
1135    /// * `id` - Unique identifier for the gate
1136    /// * `name` - Human-readable name for the gate
1137    /// * `center` - (x, y) coordinates for the center point
1138    /// * `radius_x` - Radius along the x-axis
1139    /// * `radius_y` - Radius along the y-axis
1140    /// * `angle` - Rotation angle in radians
1141    /// * `x_param` - Channel name for the x-axis parameter
1142    /// * `y_param` - Channel name for the y-axis parameter
1143    ///
1144    /// # Returns
1145    /// A new `Gate` with ellipse geometry
1146    ///
1147    /// # Errors
1148    /// Returns an error if the coordinates or radii are invalid (non-finite, negative radii)
1149    ///
1150    /// # Example
1151    /// ```rust
1152    /// use flow_gates::Gate;
1153    ///
1154    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1155    /// let gate = Gate::ellipse("ellipse", "Ellipse", (300.0, 400.0), 100.0, 50.0, 0.0, "FSC-A", "SSC-A")?;
1156    /// # Ok(())
1157    /// # }
1158    /// ```
1159    pub fn ellipse(
1160        id: impl Into<Arc<str>>,
1161        name: impl Into<String>,
1162        center: (f32, f32),
1163        radius_x: f32,
1164        radius_y: f32,
1165        angle: f32,
1166        x_param: impl Into<Arc<str>>,
1167        y_param: impl Into<Arc<str>>,
1168    ) -> Result<Self> {
1169        use crate::geometry::create_ellipse_geometry;
1170        let x_param_arc = x_param.into();
1171        let y_param_arc = y_param.into();
1172        let coords = vec![center];
1173        let geometry = create_ellipse_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1174        // Override radii and angle since create_ellipse_geometry may calculate them differently
1175        let geometry = match geometry {
1176            GateGeometry::Ellipse { center: c, .. } => GateGeometry::Ellipse {
1177                center: c,
1178                radius_x,
1179                radius_y,
1180                angle,
1181            },
1182            _ => geometry,
1183        };
1184        Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1185    }
1186}
1187
1188/// Builder for constructing gates with a fluent API
1189///
1190/// The `GateBuilder` provides a convenient way to construct gates step by step,
1191/// allowing you to set geometry, parameters, mode, and other properties before
1192/// finalizing the gate.
1193///
1194/// # Example
1195///
1196/// ```rust
1197/// use flow_gates::{GateBuilder, GateMode};
1198///
1199/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1200/// let gate = GateBuilder::new("my-gate", "My Gate")
1201///     .polygon(vec![(100.0, 200.0), (300.0, 200.0), (300.0, 400.0)], "FSC-A", "SSC-A")?
1202///     .mode(GateMode::Global)
1203///     .build()?;
1204/// # Ok(())
1205/// # }
1206/// ```
1207#[derive(Debug, Clone)]
1208pub struct GateBuilder {
1209    id: Arc<str>,
1210    name: String,
1211    geometry: Option<GateGeometry>,
1212    x_param: Option<Arc<str>>,
1213    y_param: Option<Arc<str>>,
1214    mode: GateMode,
1215    label_position: Option<LabelPosition>,
1216}
1217
1218impl GateBuilder {
1219    /// Create a new gate builder
1220    ///
1221    /// # Arguments
1222    /// * `id` - Unique identifier for the gate
1223    /// * `name` - Human-readable name for the gate
1224    pub fn new(id: impl Into<Arc<str>>, name: impl Into<String>) -> Self {
1225        Self {
1226            id: id.into(),
1227            name: name.into(),
1228            geometry: None,
1229            x_param: None,
1230            y_param: None,
1231            mode: GateMode::Global,
1232            label_position: None,
1233        }
1234    }
1235
1236    /// Set the geometry to a polygon
1237    ///
1238    /// This also sets the parameters from the geometry creation.
1239    ///
1240    /// # Arguments
1241    /// * `coords` - Vector of (x, y) coordinate tuples
1242    /// * `x_param` - Channel name for the x-axis parameter
1243    /// * `y_param` - Channel name for the y-axis parameter
1244    pub fn polygon(
1245        mut self,
1246        coords: Vec<(f32, f32)>,
1247        x_param: impl Into<Arc<str>>,
1248        y_param: impl Into<Arc<str>>,
1249    ) -> Result<Self> {
1250        use crate::geometry::create_polygon_geometry;
1251        let x_param_arc = x_param.into();
1252        let y_param_arc = y_param.into();
1253        let geometry = create_polygon_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1254        self.geometry = Some(geometry);
1255        self.x_param = Some(x_param_arc);
1256        self.y_param = Some(y_param_arc);
1257        Ok(self)
1258    }
1259
1260    /// Set the geometry to a rectangle
1261    ///
1262    /// This also sets the parameters from the geometry creation.
1263    ///
1264    /// # Arguments
1265    /// * `min` - (x, y) coordinates for the minimum corner
1266    /// * `max` - (x, y) coordinates for the maximum corner
1267    /// * `x_param` - Channel name for the x-axis parameter
1268    /// * `y_param` - Channel name for the y-axis parameter
1269    pub fn rectangle(
1270        mut self,
1271        min: (f32, f32),
1272        max: (f32, f32),
1273        x_param: impl Into<Arc<str>>,
1274        y_param: impl Into<Arc<str>>,
1275    ) -> Result<Self> {
1276        use crate::geometry::create_rectangle_geometry;
1277        let x_param_arc = x_param.into();
1278        let y_param_arc = y_param.into();
1279        let coords = vec![min, max];
1280        let geometry =
1281            create_rectangle_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1282        self.geometry = Some(geometry);
1283        self.x_param = Some(x_param_arc);
1284        self.y_param = Some(y_param_arc);
1285        Ok(self)
1286    }
1287
1288    /// Set the geometry to an ellipse
1289    ///
1290    /// This also sets the parameters from the geometry creation.
1291    ///
1292    /// # Arguments
1293    /// * `center` - (x, y) coordinates for the center point
1294    /// * `radius_x` - Radius along the x-axis
1295    /// * `radius_y` - Radius along the y-axis
1296    /// * `angle` - Rotation angle in radians
1297    /// * `x_param` - Channel name for the x-axis parameter
1298    /// * `y_param` - Channel name for the y-axis parameter
1299    pub fn ellipse(
1300        mut self,
1301        center: (f32, f32),
1302        radius_x: f32,
1303        radius_y: f32,
1304        angle: f32,
1305        x_param: impl Into<Arc<str>>,
1306        y_param: impl Into<Arc<str>>,
1307    ) -> Result<Self> {
1308        use crate::geometry::create_ellipse_geometry;
1309        let x_param_arc = x_param.into();
1310        let y_param_arc = y_param.into();
1311        let coords = vec![center];
1312        let geometry = create_ellipse_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1313        // Override radii and angle
1314        let geometry = match geometry {
1315            GateGeometry::Ellipse { center: c, .. } => GateGeometry::Ellipse {
1316                center: c,
1317                radius_x,
1318                radius_y,
1319                angle,
1320            },
1321            _ => geometry,
1322        };
1323        self.geometry = Some(geometry);
1324        self.x_param = Some(x_param_arc);
1325        self.y_param = Some(y_param_arc);
1326        Ok(self)
1327    }
1328
1329    /// Set the parameters (channels) this gate operates on
1330    ///
1331    /// # Arguments
1332    /// * `x` - Channel name for the x-axis parameter
1333    /// * `y` - Channel name for the y-axis parameter
1334    pub fn parameters(mut self, x: impl Into<Arc<str>>, y: impl Into<Arc<str>>) -> Self {
1335        self.x_param = Some(x.into());
1336        self.y_param = Some(y.into());
1337        self
1338    }
1339
1340    /// Set the gate mode (scope)
1341    ///
1342    /// # Arguments
1343    /// * `mode` - The gate mode (Global, FileSpecific, or FileGroup)
1344    pub fn mode(mut self, mode: GateMode) -> Self {
1345        self.mode = mode;
1346        self
1347    }
1348
1349    /// Set the label position
1350    ///
1351    /// # Arguments
1352    /// * `position` - The label position as an offset from the first node
1353    pub fn label_position(mut self, position: LabelPosition) -> Self {
1354        self.label_position = Some(position);
1355        self
1356    }
1357
1358    /// Build the gate from the builder
1359    ///
1360    /// # Returns
1361    /// A new `Gate` instance
1362    ///
1363    /// # Errors
1364    /// Returns an error if:
1365    /// - Geometry is not set
1366    /// - Parameters are not set
1367    /// - Builder is in an invalid state
1368    pub fn build(self) -> Result<Gate> {
1369        let geometry = self.geometry.ok_or_else(|| {
1370            GateError::invalid_builder_state("geometry", "Geometry must be set before building")
1371        })?;
1372        let x_param = self.x_param.ok_or_else(|| {
1373            GateError::invalid_builder_state("x_param", "X parameter must be set before building")
1374        })?;
1375        let y_param = self.y_param.ok_or_else(|| {
1376            GateError::invalid_builder_state("y_param", "Y parameter must be set before building")
1377        })?;
1378
1379        Ok(Gate {
1380            id: self.id,
1381            name: self.name,
1382            geometry,
1383            mode: self.mode,
1384            parameters: (x_param, y_param),
1385            label_position: self.label_position,
1386        })
1387    }
1388}
1389
1390// Custom serde helpers for Arc<str> types
1391mod arc_str_serde {
1392    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1393    use std::sync::Arc;
1394
1395    pub fn serialize<S>(arc: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
1396    where
1397        S: Serializer,
1398    {
1399        arc.as_ref().serialize(serializer)
1400    }
1401
1402    pub fn deserialize<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
1403    where
1404        D: Deserializer<'de>,
1405    {
1406        let s = String::deserialize(deserializer)?;
1407        Ok(Arc::from(s.as_str()))
1408    }
1409}
1410
1411mod arc_str_vec {
1412    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1413    use std::sync::Arc;
1414
1415    pub fn serialize<S>(vec: &Vec<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
1416    where
1417        S: Serializer,
1418    {
1419        let strings: Vec<&str> = vec.iter().map(|arc| arc.as_ref()).collect();
1420        strings.serialize(serializer)
1421    }
1422
1423    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Arc<str>>, D::Error>
1424    where
1425        D: Deserializer<'de>,
1426    {
1427        let vec = Vec::<String>::deserialize(deserializer)?;
1428        Ok(vec.into_iter().map(|s| Arc::from(s.as_str())).collect())
1429    }
1430}
1431
1432mod arc_str_pair {
1433    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1434    use std::sync::Arc;
1435
1436    pub fn serialize<S>(pair: &(Arc<str>, Arc<str>), serializer: S) -> Result<S::Ok, S::Error>
1437    where
1438        S: Serializer,
1439    {
1440        (pair.0.as_ref(), pair.1.as_ref()).serialize(serializer)
1441    }
1442
1443    pub fn deserialize<'de, D>(deserializer: D) -> Result<(Arc<str>, Arc<str>), D::Error>
1444    where
1445        D: Deserializer<'de>,
1446    {
1447        let (s1, s2) = <(String, String)>::deserialize(deserializer)?;
1448        Ok((Arc::from(s1.as_str()), Arc::from(s2.as_str())))
1449    }
1450}
1451
1452mod arc_str_hashmap {
1453    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1454    use std::collections::HashMap;
1455    use std::sync::Arc;
1456
1457    pub fn serialize<S>(map: &HashMap<Arc<str>, f32>, serializer: S) -> Result<S::Ok, S::Error>
1458    where
1459        S: Serializer,
1460    {
1461        let string_map: HashMap<&str, f32> = map.iter().map(|(k, v)| (k.as_ref(), *v)).collect();
1462        string_map.serialize(serializer)
1463    }
1464
1465    pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<Arc<str>, f32>, D::Error>
1466    where
1467        D: Deserializer<'de>,
1468    {
1469        let map = HashMap::<String, f32>::deserialize(deserializer)?;
1470        Ok(map
1471            .into_iter()
1472            .map(|(k, v)| (Arc::from(k.as_str()), v))
1473            .collect())
1474    }
1475}