Skip to main content

clasp_bridge/
transform.rs

1//! Enhanced value transformation system
2//!
3//! This module provides comprehensive value transformation capabilities:
4//! - Expression engine for JavaScript-like math expressions
5//! - Lookup tables for discrete value mapping
6//! - Curve functions (easing, exponential, logarithmic, bezier)
7//! - Aggregation functions (average, sum, min, max, moving average)
8//! - Conditional transforms based on value or metadata
9//! - JSON path extraction and injection
10
11use clasp_core::Value;
12use evalexpr::{eval_with_context_mut, ContextWithMutableVariables, HashMapContext};
13use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, VecDeque};
15use std::f64::consts::PI;
16use tracing::warn;
17
18/// Enhanced transform that can be applied to values
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum Transform {
22    /// No transformation (passthrough)
23    Identity,
24
25    /// Mathematical expression evaluation
26    /// Variables: `value`, `index`, `time`
27    Expression { expr: String },
28
29    /// Scale from one range to another
30    Scale {
31        from_min: f64,
32        from_max: f64,
33        to_min: f64,
34        to_max: f64,
35    },
36
37    /// Clamp to range
38    Clamp { min: f64, max: f64 },
39
40    /// Invert (1 - x for normalized values)
41    Invert,
42
43    /// Convert to integer
44    ToInt,
45
46    /// Convert to float
47    ToFloat,
48
49    /// Lookup table for discrete value mapping
50    Lookup {
51        table: HashMap<String, Value>,
52        default: Option<Value>,
53    },
54
55    /// Easing curve functions
56    Curve { curve_type: CurveType },
57
58    /// Quantize to discrete steps
59    Quantize { steps: u32 },
60
61    /// Dead zone (values below threshold become 0)
62    DeadZone { threshold: f64 },
63
64    /// Smoothing with exponential moving average
65    Smooth { factor: f64 },
66
67    /// Rate limiter (max change per update)
68    RateLimit { max_delta: f64 },
69
70    /// Threshold trigger (output 0 or 1 based on threshold)
71    Threshold { value: f64, mode: ThresholdMode },
72
73    /// Modulo operation
74    Modulo { divisor: f64 },
75
76    /// Absolute value
77    Abs,
78
79    /// Negate
80    Negate,
81
82    /// Power function
83    Power { exponent: f64 },
84
85    /// Logarithm
86    Log { base: Option<f64> },
87
88    /// Round to decimal places
89    Round { decimals: u32 },
90
91    /// Chain multiple transforms
92    Chain { transforms: Vec<Transform> },
93
94    /// Conditional transform based on value
95    Conditional {
96        condition: Condition,
97        if_true: Box<Transform>,
98        if_false: Box<Transform>,
99    },
100
101    /// JSON path extraction (for complex values)
102    JsonPath { path: String },
103
104    /// Map to different value type
105    MapType {
106        from_type: ValueType,
107        to_type: ValueType,
108    },
109
110    /// Bitwise operations
111    Bitwise {
112        operation: BitwiseOp,
113        operand: Option<i64>,
114    },
115}
116
117/// Curve types for non-linear transforms
118#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
119#[serde(rename_all = "snake_case")]
120pub enum CurveType {
121    /// Linear (no curve)
122    Linear,
123    /// Ease in (slow start)
124    EaseIn,
125    /// Ease out (slow end)
126    EaseOut,
127    /// Ease in and out
128    EaseInOut,
129    /// Quadratic ease in
130    QuadIn,
131    /// Quadratic ease out
132    QuadOut,
133    /// Quadratic ease in-out
134    QuadInOut,
135    /// Cubic ease in
136    CubicIn,
137    /// Cubic ease out
138    CubicOut,
139    /// Cubic ease in-out
140    CubicInOut,
141    /// Exponential ease in
142    ExpoIn,
143    /// Exponential ease out
144    ExpoOut,
145    /// Exponential ease in-out
146    ExpoInOut,
147    /// Sine ease in
148    SineIn,
149    /// Sine ease out
150    SineOut,
151    /// Sine ease in-out
152    SineInOut,
153    /// Circular ease in
154    CircIn,
155    /// Circular ease out
156    CircOut,
157    /// Circular ease in-out
158    CircInOut,
159    /// Elastic ease in
160    ElasticIn,
161    /// Elastic ease out
162    ElasticOut,
163    /// Bounce ease out
164    BounceOut,
165    /// Custom bezier curve
166    Bezier { x1: f64, y1: f64, x2: f64, y2: f64 },
167}
168
169/// Threshold mode
170#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
171#[serde(rename_all = "snake_case")]
172pub enum ThresholdMode {
173    /// Output 1 if above, 0 if below
174    Above,
175    /// Output 1 if below, 0 if above
176    Below,
177    /// Output 1 if equal (within tolerance)
178    Equal,
179}
180
181/// Condition for conditional transforms
182#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(tag = "type", rename_all = "snake_case")]
184pub enum Condition {
185    /// Value is greater than threshold
186    GreaterThan { value: f64 },
187    /// Value is less than threshold
188    LessThan { value: f64 },
189    /// Value equals (within tolerance)
190    Equals { value: f64, tolerance: Option<f64> },
191    /// Value is in range
192    InRange { min: f64, max: f64 },
193    /// Expression evaluates to true
194    Expression { expr: String },
195    /// Logical AND of conditions
196    And { conditions: Vec<Condition> },
197    /// Logical OR of conditions
198    Or { conditions: Vec<Condition> },
199    /// Logical NOT
200    Not { condition: Box<Condition> },
201}
202
203/// Value types for type conversion
204#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
205#[serde(rename_all = "snake_case")]
206pub enum ValueType {
207    Int,
208    Float,
209    Bool,
210    String,
211}
212
213/// Bitwise operations
214#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
215#[serde(rename_all = "snake_case")]
216pub enum BitwiseOp {
217    And,
218    Or,
219    Xor,
220    Not,
221    ShiftLeft,
222    ShiftRight,
223    GetBit,
224    SetBit,
225    ClearBit,
226}
227
228/// Transform state for stateful transforms (smoothing, rate limiting, etc.)
229#[derive(Debug, Clone, Default)]
230pub struct TransformState {
231    /// Last value for smoothing/rate limiting
232    pub last_value: Option<f64>,
233    /// Moving average window
234    pub window: VecDeque<f64>,
235    /// Timestamp of last update
236    pub last_time: Option<std::time::Instant>,
237}
238
239impl Transform {
240    /// Apply the transform to a value
241    pub fn apply(&self, value: &Value, state: &mut TransformState) -> Value {
242        match self {
243            Transform::Identity => value.clone(),
244
245            Transform::Expression { expr } => self.eval_expression(expr, value),
246
247            Transform::Scale {
248                from_min,
249                from_max,
250                to_min,
251                to_max,
252            } => {
253                if let Some(v) = value.as_f64() {
254                    let normalized = (v - from_min) / (from_max - from_min);
255                    let scaled = to_min + normalized * (to_max - to_min);
256                    Value::Float(scaled)
257                } else {
258                    value.clone()
259                }
260            }
261
262            Transform::Clamp { min, max } => {
263                if let Some(v) = value.as_f64() {
264                    Value::Float(v.clamp(*min, *max))
265                } else {
266                    value.clone()
267                }
268            }
269
270            Transform::Invert => {
271                if let Some(v) = value.as_f64() {
272                    Value::Float(1.0 - v)
273                } else {
274                    value.clone()
275                }
276            }
277
278            Transform::ToInt => {
279                if let Some(v) = value.as_f64() {
280                    Value::Int(v as i64)
281                } else {
282                    value.clone()
283                }
284            }
285
286            Transform::ToFloat => {
287                if let Some(v) = value.as_i64() {
288                    Value::Float(v as f64)
289                } else {
290                    value.clone()
291                }
292            }
293
294            Transform::Lookup { table, default } => {
295                let key = match value {
296                    Value::Int(i) => i.to_string(),
297                    Value::Float(f) => f.to_string(),
298                    Value::String(s) => s.clone(),
299                    Value::Bool(b) => b.to_string(),
300                    _ => return default.clone().unwrap_or_else(|| value.clone()),
301                };
302                table
303                    .get(&key)
304                    .cloned()
305                    .unwrap_or_else(|| default.clone().unwrap_or_else(|| value.clone()))
306            }
307
308            Transform::Curve { curve_type } => {
309                if let Some(t) = value.as_f64() {
310                    let t = t.clamp(0.0, 1.0);
311                    Value::Float(curve_type.apply(t))
312                } else {
313                    value.clone()
314                }
315            }
316
317            Transform::Quantize { steps } => {
318                if let Some(v) = value.as_f64() {
319                    let steps = *steps as f64;
320                    let quantized = (v * steps).round() / steps;
321                    Value::Float(quantized)
322                } else {
323                    value.clone()
324                }
325            }
326
327            Transform::DeadZone { threshold } => {
328                if let Some(v) = value.as_f64() {
329                    if v.abs() < *threshold {
330                        Value::Float(0.0)
331                    } else {
332                        value.clone()
333                    }
334                } else {
335                    value.clone()
336                }
337            }
338
339            Transform::Smooth { factor } => {
340                if let Some(v) = value.as_f64() {
341                    let smoothed = if let Some(last) = state.last_value {
342                        last + factor * (v - last)
343                    } else {
344                        v
345                    };
346                    state.last_value = Some(smoothed);
347                    Value::Float(smoothed)
348                } else {
349                    value.clone()
350                }
351            }
352
353            Transform::RateLimit { max_delta } => {
354                if let Some(v) = value.as_f64() {
355                    let limited = if let Some(last) = state.last_value {
356                        let delta = v - last;
357                        if delta.abs() > *max_delta {
358                            last + max_delta * delta.signum()
359                        } else {
360                            v
361                        }
362                    } else {
363                        v
364                    };
365                    state.last_value = Some(limited);
366                    Value::Float(limited)
367                } else {
368                    value.clone()
369                }
370            }
371
372            Transform::Threshold {
373                value: thresh,
374                mode,
375            } => {
376                if let Some(v) = value.as_f64() {
377                    let result = match mode {
378                        ThresholdMode::Above => {
379                            if v > *thresh {
380                                1.0
381                            } else {
382                                0.0
383                            }
384                        }
385                        ThresholdMode::Below => {
386                            if v < *thresh {
387                                1.0
388                            } else {
389                                0.0
390                            }
391                        }
392                        ThresholdMode::Equal => {
393                            if (v - thresh).abs() < 0.001 {
394                                1.0
395                            } else {
396                                0.0
397                            }
398                        }
399                    };
400                    Value::Float(result)
401                } else {
402                    value.clone()
403                }
404            }
405
406            Transform::Modulo { divisor } => {
407                if let Some(v) = value.as_f64() {
408                    Value::Float(v % divisor)
409                } else {
410                    value.clone()
411                }
412            }
413
414            Transform::Abs => {
415                if let Some(v) = value.as_f64() {
416                    Value::Float(v.abs())
417                } else if let Some(v) = value.as_i64() {
418                    Value::Int(v.abs())
419                } else {
420                    value.clone()
421                }
422            }
423
424            Transform::Negate => {
425                if let Some(v) = value.as_f64() {
426                    Value::Float(-v)
427                } else if let Some(v) = value.as_i64() {
428                    Value::Int(-v)
429                } else {
430                    value.clone()
431                }
432            }
433
434            Transform::Power { exponent } => {
435                if let Some(v) = value.as_f64() {
436                    Value::Float(v.powf(*exponent))
437                } else {
438                    value.clone()
439                }
440            }
441
442            Transform::Log { base } => {
443                if let Some(v) = value.as_f64() {
444                    if v > 0.0 {
445                        let result = match base {
446                            Some(b) => v.log(*b),
447                            None => v.ln(),
448                        };
449                        Value::Float(result)
450                    } else {
451                        value.clone()
452                    }
453                } else {
454                    value.clone()
455                }
456            }
457
458            Transform::Round { decimals } => {
459                if let Some(v) = value.as_f64() {
460                    let factor = 10_f64.powi(*decimals as i32);
461                    Value::Float((v * factor).round() / factor)
462                } else {
463                    value.clone()
464                }
465            }
466
467            Transform::Chain { transforms } => {
468                let mut result = value.clone();
469                for transform in transforms {
470                    result = transform.apply(&result, state);
471                }
472                result
473            }
474
475            Transform::Conditional {
476                condition,
477                if_true,
478                if_false,
479            } => {
480                if condition.evaluate(value) {
481                    if_true.apply(value, state)
482                } else {
483                    if_false.apply(value, state)
484                }
485            }
486
487            Transform::JsonPath { path } => self.extract_json_path(value, path),
488
489            Transform::MapType { to_type, .. } => match to_type {
490                ValueType::Int => {
491                    if let Some(v) = value.as_f64() {
492                        Value::Int(v as i64)
493                    } else if let Some(s) = value.as_str().map(|s| s.to_string()) {
494                        s.parse::<i64>()
495                            .map(Value::Int)
496                            .unwrap_or_else(|_| value.clone())
497                    } else {
498                        value.clone()
499                    }
500                }
501                ValueType::Float => {
502                    if let Some(v) = value.as_i64() {
503                        Value::Float(v as f64)
504                    } else if let Some(s) = value.as_str().map(|s| s.to_string()) {
505                        s.parse::<f64>()
506                            .map(Value::Float)
507                            .unwrap_or_else(|_| value.clone())
508                    } else {
509                        value.clone()
510                    }
511                }
512                ValueType::Bool => {
513                    if let Some(v) = value.as_f64() {
514                        Value::Bool(v != 0.0)
515                    } else if let Some(v) = value.as_i64() {
516                        Value::Bool(v != 0)
517                    } else {
518                        value.clone()
519                    }
520                }
521                ValueType::String => match value {
522                    Value::Int(i) => Value::String(i.to_string()),
523                    Value::Float(f) => Value::String(f.to_string()),
524                    Value::Bool(b) => Value::String(b.to_string()),
525                    _ => value.clone(),
526                },
527            },
528
529            Transform::Bitwise { operation, operand } => {
530                if let Some(v) = value.as_i64() {
531                    let result = match operation {
532                        BitwiseOp::And => v & operand.unwrap_or(0),
533                        BitwiseOp::Or => v | operand.unwrap_or(0),
534                        BitwiseOp::Xor => v ^ operand.unwrap_or(0),
535                        BitwiseOp::Not => !v,
536                        BitwiseOp::ShiftLeft => v << operand.unwrap_or(1) as u32,
537                        BitwiseOp::ShiftRight => v >> operand.unwrap_or(1) as u32,
538                        BitwiseOp::GetBit => (v >> operand.unwrap_or(0) as u32) & 1,
539                        BitwiseOp::SetBit => v | (1 << operand.unwrap_or(0) as u32),
540                        BitwiseOp::ClearBit => v & !(1 << operand.unwrap_or(0) as u32),
541                    };
542                    Value::Int(result)
543                } else {
544                    value.clone()
545                }
546            }
547        }
548    }
549
550    fn eval_expression(&self, expr: &str, value: &Value) -> Value {
551        let mut context = HashMapContext::new();
552
553        // Set value variable
554        if let Some(v) = value.as_f64() {
555            let _ = context.set_value("value".to_string(), evalexpr::Value::Float(v));
556            let _ = context.set_value("x".to_string(), evalexpr::Value::Float(v));
557        } else if let Some(v) = value.as_i64() {
558            let _ = context.set_value("value".to_string(), evalexpr::Value::Int(v));
559            let _ = context.set_value("x".to_string(), evalexpr::Value::Int(v));
560        }
561
562        // Add math constants
563        let _ = context.set_value("PI".to_string(), evalexpr::Value::Float(PI));
564        let _ = context.set_value("E".to_string(), evalexpr::Value::Float(std::f64::consts::E));
565
566        match eval_with_context_mut(expr, &mut context) {
567            Ok(evalexpr::Value::Float(f)) => Value::Float(f),
568            Ok(evalexpr::Value::Int(i)) => Value::Int(i),
569            Ok(evalexpr::Value::Boolean(b)) => Value::Bool(b),
570            Ok(evalexpr::Value::String(s)) => Value::String(s),
571            Ok(_) => value.clone(),
572            Err(e) => {
573                warn!("Expression evaluation failed: {}", e);
574                value.clone()
575            }
576        }
577    }
578
579    fn extract_json_path(&self, value: &Value, path: &str) -> Value {
580        // Convert Value to serde_json::Value for jsonpath
581        let json_value = Self::value_to_json(value);
582
583        match jsonpath_lib::select(&json_value, path) {
584            Ok(results) => {
585                if results.len() == 1 {
586                    Self::json_to_value(results[0])
587                } else if !results.is_empty() {
588                    let arr: Vec<Value> = results.iter().map(|v| Self::json_to_value(v)).collect();
589                    Value::Array(arr)
590                } else {
591                    Value::Null
592                }
593            }
594            Err(e) => {
595                warn!("JSON path extraction failed: {}", e);
596                value.clone()
597            }
598        }
599    }
600
601    fn value_to_json(value: &Value) -> serde_json::Value {
602        match value {
603            Value::Int(i) => serde_json::Value::Number((*i).into()),
604            Value::Float(f) => serde_json::Number::from_f64(*f)
605                .map(serde_json::Value::Number)
606                .unwrap_or(serde_json::Value::Null),
607            Value::String(s) => serde_json::Value::String(s.clone()),
608            Value::Bool(b) => serde_json::Value::Bool(*b),
609            Value::Bytes(b) => serde_json::Value::String(base64::encode(b)),
610            Value::Array(arr) => {
611                serde_json::Value::Array(arr.iter().map(Self::value_to_json).collect())
612            }
613            Value::Map(m) => {
614                let obj: serde_json::Map<String, serde_json::Value> = m
615                    .iter()
616                    .map(|(k, v)| (k.clone(), Self::value_to_json(v)))
617                    .collect();
618                serde_json::Value::Object(obj)
619            }
620            Value::Null => serde_json::Value::Null,
621        }
622    }
623
624    fn json_to_value(json: &serde_json::Value) -> Value {
625        match json {
626            serde_json::Value::Null => Value::Null,
627            serde_json::Value::Bool(b) => Value::Bool(*b),
628            serde_json::Value::Number(n) => {
629                if let Some(i) = n.as_i64() {
630                    Value::Int(i)
631                } else if let Some(f) = n.as_f64() {
632                    Value::Float(f)
633                } else {
634                    Value::Null
635                }
636            }
637            serde_json::Value::String(s) => Value::String(s.clone()),
638            serde_json::Value::Array(arr) => {
639                Value::Array(arr.iter().map(Self::json_to_value).collect())
640            }
641            serde_json::Value::Object(_) => {
642                // Convert object to JSON string
643                Value::String(json.to_string())
644            }
645        }
646    }
647}
648
649impl CurveType {
650    /// Apply the easing curve to a normalized value (0-1)
651    pub fn apply(&self, t: f64) -> f64 {
652        match self {
653            CurveType::Linear => t,
654            CurveType::EaseIn => t * t,
655            CurveType::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
656            CurveType::EaseInOut => {
657                if t < 0.5 {
658                    2.0 * t * t
659                } else {
660                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
661                }
662            }
663            CurveType::QuadIn => t * t,
664            CurveType::QuadOut => 1.0 - (1.0 - t).powi(2),
665            CurveType::QuadInOut => {
666                if t < 0.5 {
667                    2.0 * t * t
668                } else {
669                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
670                }
671            }
672            CurveType::CubicIn => t * t * t,
673            CurveType::CubicOut => 1.0 - (1.0 - t).powi(3),
674            CurveType::CubicInOut => {
675                if t < 0.5 {
676                    4.0 * t * t * t
677                } else {
678                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
679                }
680            }
681            CurveType::ExpoIn => {
682                if t == 0.0 {
683                    0.0
684                } else {
685                    2_f64.powf(10.0 * t - 10.0)
686                }
687            }
688            CurveType::ExpoOut => {
689                if t == 1.0 {
690                    1.0
691                } else {
692                    1.0 - 2_f64.powf(-10.0 * t)
693                }
694            }
695            CurveType::ExpoInOut => {
696                if t == 0.0 {
697                    0.0
698                } else if t == 1.0 {
699                    1.0
700                } else if t < 0.5 {
701                    2_f64.powf(20.0 * t - 10.0) / 2.0
702                } else {
703                    (2.0 - 2_f64.powf(-20.0 * t + 10.0)) / 2.0
704                }
705            }
706            CurveType::SineIn => 1.0 - (t * PI / 2.0).cos(),
707            CurveType::SineOut => (t * PI / 2.0).sin(),
708            CurveType::SineInOut => -((PI * t).cos() - 1.0) / 2.0,
709            CurveType::CircIn => 1.0 - (1.0 - t * t).sqrt(),
710            CurveType::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
711            CurveType::CircInOut => {
712                if t < 0.5 {
713                    (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
714                } else {
715                    ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
716                }
717            }
718            CurveType::ElasticIn => {
719                if t == 0.0 {
720                    0.0
721                } else if t == 1.0 {
722                    1.0
723                } else {
724                    let c4 = (2.0 * PI) / 3.0;
725                    -2_f64.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * c4).sin()
726                }
727            }
728            CurveType::ElasticOut => {
729                if t == 0.0 {
730                    0.0
731                } else if t == 1.0 {
732                    1.0
733                } else {
734                    let c4 = (2.0 * PI) / 3.0;
735                    2_f64.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
736                }
737            }
738            CurveType::BounceOut => {
739                let n1 = 7.5625;
740                let d1 = 2.75;
741                if t < 1.0 / d1 {
742                    n1 * t * t
743                } else if t < 2.0 / d1 {
744                    let t = t - 1.5 / d1;
745                    n1 * t * t + 0.75
746                } else if t < 2.5 / d1 {
747                    let t = t - 2.25 / d1;
748                    n1 * t * t + 0.9375
749                } else {
750                    let t = t - 2.625 / d1;
751                    n1 * t * t + 0.984375
752                }
753            }
754            CurveType::Bezier { x1, y1, x2, y2 } => {
755                // Approximate cubic bezier curve
756                Self::cubic_bezier(t, *x1, *y1, *x2, *y2)
757            }
758        }
759    }
760
761    fn cubic_bezier(t: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
762        // Newton-Raphson iteration to find t for given x
763        let mut guess = t;
764        for _ in 0..8 {
765            let x = Self::bezier_x(guess, x1, x2) - t;
766            if x.abs() < 1e-6 {
767                break;
768            }
769            let dx = Self::bezier_dx(guess, x1, x2);
770            if dx.abs() < 1e-6 {
771                break;
772            }
773            guess -= x / dx;
774        }
775        Self::bezier_y(guess, y1, y2)
776    }
777
778    fn bezier_x(t: f64, x1: f64, x2: f64) -> f64 {
779        let t2 = t * t;
780        let t3 = t2 * t;
781        let mt = 1.0 - t;
782        let mt2 = mt * mt;
783        3.0 * mt2 * t * x1 + 3.0 * mt * t2 * x2 + t3
784    }
785
786    fn bezier_y(t: f64, y1: f64, y2: f64) -> f64 {
787        let t2 = t * t;
788        let t3 = t2 * t;
789        let mt = 1.0 - t;
790        let mt2 = mt * mt;
791        3.0 * mt2 * t * y1 + 3.0 * mt * t2 * y2 + t3
792    }
793
794    fn bezier_dx(t: f64, x1: f64, x2: f64) -> f64 {
795        let t2 = t * t;
796        let mt = 1.0 - t;
797        3.0 * mt * mt * x1 + 6.0 * mt * t * (x2 - x1) + 3.0 * t2 * (1.0 - x2)
798    }
799}
800
801impl Condition {
802    /// Evaluate the condition against a value
803    pub fn evaluate(&self, value: &Value) -> bool {
804        match self {
805            Condition::GreaterThan { value: threshold } => {
806                value.as_f64().map(|v| v > *threshold).unwrap_or(false)
807            }
808            Condition::LessThan { value: threshold } => {
809                value.as_f64().map(|v| v < *threshold).unwrap_or(false)
810            }
811            Condition::Equals {
812                value: target,
813                tolerance,
814            } => {
815                if let Some(v) = value.as_f64() {
816                    let tol = tolerance.unwrap_or(0.001);
817                    (v - target).abs() < tol
818                } else {
819                    false
820                }
821            }
822            Condition::InRange { min, max } => value
823                .as_f64()
824                .map(|v| v >= *min && v <= *max)
825                .unwrap_or(false),
826            Condition::Expression { expr } => {
827                let mut context = HashMapContext::new();
828                if let Some(v) = value.as_f64() {
829                    let _ = context.set_value("value".to_string(), evalexpr::Value::Float(v));
830                }
831                eval_with_context_mut(expr, &mut context)
832                    .map(|v| v.as_boolean().unwrap_or(false))
833                    .unwrap_or(false)
834            }
835            Condition::And { conditions } => conditions.iter().all(|c| c.evaluate(value)),
836            Condition::Or { conditions } => conditions.iter().any(|c| c.evaluate(value)),
837            Condition::Not { condition } => !condition.evaluate(value),
838        }
839    }
840}
841
842/// Aggregator for combining multiple values
843#[derive(Debug, Clone, Serialize, Deserialize)]
844#[serde(tag = "type", rename_all = "snake_case")]
845pub enum Aggregator {
846    /// Average of all values
847    Average,
848    /// Sum of all values
849    Sum,
850    /// Minimum value
851    Min,
852    /// Maximum value
853    Max,
854    /// Most recent value
855    Latest,
856    /// First value
857    First,
858    /// Count of values
859    Count,
860    /// Moving average over window
861    MovingAverage { window_size: usize },
862    /// Rate of change (delta per second)
863    RateOfChange,
864    /// Standard deviation
865    StdDev,
866}
867
868/// Aggregator state
869#[derive(Debug, Clone, Default)]
870pub struct AggregatorState {
871    values: VecDeque<f64>,
872    last_value: Option<f64>,
873    last_time: Option<std::time::Instant>,
874    window_size: usize,
875}
876
877impl Aggregator {
878    /// Create a new aggregator state
879    pub fn new_state(&self) -> AggregatorState {
880        let window_size = match self {
881            Aggregator::MovingAverage { window_size } => *window_size,
882            _ => 100,
883        };
884        AggregatorState {
885            window_size,
886            ..Default::default()
887        }
888    }
889
890    /// Add a value and get the aggregated result
891    pub fn add(&self, value: f64, state: &mut AggregatorState) -> f64 {
892        // Add to window
893        state.values.push_back(value);
894        while state.values.len() > state.window_size {
895            state.values.pop_front();
896        }
897
898        let result = match self {
899            Aggregator::Average => state.values.iter().sum::<f64>() / state.values.len() as f64,
900            Aggregator::Sum => state.values.iter().sum(),
901            Aggregator::Min => state.values.iter().cloned().fold(f64::INFINITY, f64::min),
902            Aggregator::Max => state
903                .values
904                .iter()
905                .cloned()
906                .fold(f64::NEG_INFINITY, f64::max),
907            Aggregator::Latest => value,
908            Aggregator::First => state.values.front().copied().unwrap_or(value),
909            Aggregator::Count => state.values.len() as f64,
910            Aggregator::MovingAverage { .. } => {
911                state.values.iter().sum::<f64>() / state.values.len() as f64
912            }
913            Aggregator::RateOfChange => {
914                let now = std::time::Instant::now();
915                let rate = if let (Some(last), Some(time)) = (state.last_value, state.last_time) {
916                    let dt = now.duration_since(time).as_secs_f64();
917                    if dt > 0.0 {
918                        (value - last) / dt
919                    } else {
920                        0.0
921                    }
922                } else {
923                    0.0
924                };
925                state.last_time = Some(now);
926                rate
927            }
928            Aggregator::StdDev => {
929                if state.values.is_empty() {
930                    0.0
931                } else {
932                    let mean = state.values.iter().sum::<f64>() / state.values.len() as f64;
933                    let variance = state.values.iter().map(|v| (v - mean).powi(2)).sum::<f64>()
934                        / state.values.len() as f64;
935                    variance.sqrt()
936                }
937            }
938        };
939
940        state.last_value = Some(value);
941        result
942    }
943}
944
945/// Helper for base64 encoding (used in JSON path)
946mod base64 {
947    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
948
949    pub fn encode(data: &[u8]) -> String {
950        let mut result = String::new();
951        for chunk in data.chunks(3) {
952            let mut n = (chunk[0] as u32) << 16;
953            if chunk.len() > 1 {
954                n |= (chunk[1] as u32) << 8;
955            }
956            if chunk.len() > 2 {
957                n |= chunk[2] as u32;
958            }
959
960            result.push(CHARS[(n >> 18 & 0x3F) as usize] as char);
961            result.push(CHARS[(n >> 12 & 0x3F) as usize] as char);
962            if chunk.len() > 1 {
963                result.push(CHARS[(n >> 6 & 0x3F) as usize] as char);
964            } else {
965                result.push('=');
966            }
967            if chunk.len() > 2 {
968                result.push(CHARS[(n & 0x3F) as usize] as char);
969            } else {
970                result.push('=');
971            }
972        }
973        result
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use super::*;
980
981    #[test]
982    fn test_expression_transform() {
983        let transform = Transform::Expression {
984            expr: "value * 2 + 10".to_string(),
985        };
986        let mut state = TransformState::default();
987        let result = transform.apply(&Value::Float(5.0), &mut state);
988        assert_eq!(result.as_f64(), Some(20.0));
989    }
990
991    #[test]
992    fn test_lookup_transform() {
993        let mut table = HashMap::new();
994        table.insert("0".to_string(), Value::String("off".to_string()));
995        table.insert("1".to_string(), Value::String("on".to_string()));
996
997        let transform = Transform::Lookup {
998            table,
999            default: Some(Value::String("unknown".to_string())),
1000        };
1001        let mut state = TransformState::default();
1002
1003        assert_eq!(
1004            transform.apply(&Value::Int(0), &mut state).as_str(),
1005            Some("off")
1006        );
1007        assert_eq!(
1008            transform.apply(&Value::Int(1), &mut state).as_str(),
1009            Some("on")
1010        );
1011        assert_eq!(
1012            transform.apply(&Value::Int(99), &mut state).as_str(),
1013            Some("unknown")
1014        );
1015    }
1016
1017    #[test]
1018    fn test_curve_ease_in() {
1019        let curve = CurveType::EaseIn;
1020        assert!((curve.apply(0.0) - 0.0).abs() < 0.001);
1021        assert!((curve.apply(0.5) - 0.25).abs() < 0.001);
1022        assert!((curve.apply(1.0) - 1.0).abs() < 0.001);
1023    }
1024
1025    #[test]
1026    fn test_curve_ease_out() {
1027        let curve = CurveType::EaseOut;
1028        assert!((curve.apply(0.0) - 0.0).abs() < 0.001);
1029        assert!((curve.apply(0.5) - 0.75).abs() < 0.001);
1030        assert!((curve.apply(1.0) - 1.0).abs() < 0.001);
1031    }
1032
1033    #[test]
1034    fn test_smooth_transform() {
1035        let transform = Transform::Smooth { factor: 0.5 };
1036        let mut state = TransformState::default();
1037
1038        // First value passes through
1039        let r1 = transform.apply(&Value::Float(1.0), &mut state);
1040        assert_eq!(r1.as_f64(), Some(1.0));
1041
1042        // Second value is smoothed
1043        let r2 = transform.apply(&Value::Float(0.0), &mut state);
1044        assert!((r2.as_f64().unwrap() - 0.5).abs() < 0.001);
1045    }
1046
1047    #[test]
1048    fn test_chain_transform() {
1049        let transform = Transform::Chain {
1050            transforms: vec![
1051                Transform::Scale {
1052                    from_min: 0.0,
1053                    from_max: 127.0,
1054                    to_min: 0.0,
1055                    to_max: 1.0,
1056                },
1057                Transform::Curve {
1058                    curve_type: CurveType::EaseIn,
1059                },
1060            ],
1061        };
1062        let mut state = TransformState::default();
1063
1064        let result = transform.apply(&Value::Float(63.5), &mut state);
1065        // 63.5/127 = 0.5, then ease-in: 0.5^2 = 0.25
1066        assert!((result.as_f64().unwrap() - 0.25).abs() < 0.01);
1067    }
1068
1069    #[test]
1070    fn test_conditional_transform() {
1071        let transform = Transform::Conditional {
1072            condition: Condition::GreaterThan { value: 0.5 },
1073            if_true: Box::new(Transform::Expression {
1074                expr: "1.0".to_string(),
1075            }),
1076            if_false: Box::new(Transform::Expression {
1077                expr: "0.0".to_string(),
1078            }),
1079        };
1080        let mut state = TransformState::default();
1081
1082        assert_eq!(
1083            transform.apply(&Value::Float(0.7), &mut state).as_f64(),
1084            Some(1.0)
1085        );
1086        assert_eq!(
1087            transform.apply(&Value::Float(0.3), &mut state).as_f64(),
1088            Some(0.0)
1089        );
1090    }
1091
1092    #[test]
1093    fn test_aggregator_average() {
1094        let agg = Aggregator::Average;
1095        let mut state = agg.new_state();
1096
1097        assert_eq!(agg.add(1.0, &mut state), 1.0);
1098        assert_eq!(agg.add(2.0, &mut state), 1.5);
1099        assert_eq!(agg.add(3.0, &mut state), 2.0);
1100    }
1101
1102    #[test]
1103    fn test_aggregator_moving_average() {
1104        let agg = Aggregator::MovingAverage { window_size: 3 };
1105        let mut state = agg.new_state();
1106
1107        agg.add(1.0, &mut state);
1108        agg.add(2.0, &mut state);
1109        agg.add(3.0, &mut state);
1110        let result = agg.add(4.0, &mut state);
1111        // Window: [2, 3, 4], average = 3
1112        assert_eq!(result, 3.0);
1113    }
1114}