Skip to main content

json_eval_rs/rlogic/
compiled.rs

1use crate::jsoneval::path_utils;
2use ahash::AHashMap;
3use serde::Serialize;
4use serde_json::Value;
5
6/// Unique identifier for compiled logic expressions
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
8pub struct LogicId(pub(crate) u64);
9
10/// Compiled JSON Logic expression optimized for fast evaluation
11#[derive(Debug, Clone)]
12pub enum CompiledLogic {
13    // Literal values
14    Null,
15    Bool(bool),
16    Number(f64),
17    String(String),
18    Array(Vec<CompiledLogic>),
19
20    // Variable access
21    Var(String, Option<Box<CompiledLogic>>), // name, default
22    Ref(String, Option<Box<CompiledLogic>>), // JSON Schema reference path, default
23
24    // Logical operators
25    And(Vec<CompiledLogic>),
26    Or(Vec<CompiledLogic>),
27    Not(Box<CompiledLogic>),
28    If(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // condition, then, else
29
30    // Comparison operators
31    Equal(Box<CompiledLogic>, Box<CompiledLogic>),
32    StrictEqual(Box<CompiledLogic>, Box<CompiledLogic>),
33    NotEqual(Box<CompiledLogic>, Box<CompiledLogic>),
34    StrictNotEqual(Box<CompiledLogic>, Box<CompiledLogic>),
35    LessThan(Box<CompiledLogic>, Box<CompiledLogic>),
36    LessThanOrEqual(Box<CompiledLogic>, Box<CompiledLogic>),
37    GreaterThan(Box<CompiledLogic>, Box<CompiledLogic>),
38    GreaterThanOrEqual(Box<CompiledLogic>, Box<CompiledLogic>),
39
40    // Arithmetic operators
41    Add(Vec<CompiledLogic>),
42    Subtract(Vec<CompiledLogic>),
43    Multiply(Vec<CompiledLogic>),
44    Divide(Vec<CompiledLogic>),
45    Modulo(Box<CompiledLogic>, Box<CompiledLogic>),
46    Power(Box<CompiledLogic>, Box<CompiledLogic>),
47
48    // Array operators
49    Map(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
50    Filter(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
51    Reduce(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // array, logic, initial
52    All(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
53    Some(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
54    None(Box<CompiledLogic>, Box<CompiledLogic>), // array, logic
55    Merge(Vec<CompiledLogic>),
56    In(Box<CompiledLogic>, Box<CompiledLogic>), // value, array
57
58    // String operators
59    Cat(Vec<CompiledLogic>),
60    Substr(
61        Box<CompiledLogic>,
62        Box<CompiledLogic>,
63        Option<Box<CompiledLogic>>,
64    ), // string, start, length
65
66    // Utility operators
67    Missing(Vec<String>),
68    MissingSome(Box<CompiledLogic>, Vec<String>), // minimum, keys
69
70    // Custom operators - Math
71    Abs(Box<CompiledLogic>),
72    Max(Vec<CompiledLogic>),
73    Min(Vec<CompiledLogic>),
74    Pow(Box<CompiledLogic>, Box<CompiledLogic>),
75    Round(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
76    RoundUp(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
77    RoundDown(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals
78    Ceiling(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, significance (Excel CEILING)
79    Floor(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, significance (Excel FLOOR)
80    Trunc(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // value, decimals (Excel TRUNC)
81    Mround(Box<CompiledLogic>, Box<CompiledLogic>),        // value, multiple (Excel MROUND)
82
83    // Custom operators - String
84    Length(Box<CompiledLogic>),
85    Search(
86        Box<CompiledLogic>,
87        Box<CompiledLogic>,
88        Option<Box<CompiledLogic>>,
89    ), // find, within, start_num
90    Left(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // text, num_chars
91    Right(Box<CompiledLogic>, Option<Box<CompiledLogic>>), // text, num_chars
92    Mid(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // text, start, num_chars
93    Len(Box<CompiledLogic>),
94    SplitText(
95        Box<CompiledLogic>,
96        Box<CompiledLogic>,
97        Option<Box<CompiledLogic>>,
98    ), // value, separator, index
99    Concat(Vec<CompiledLogic>),
100    SplitValue(Box<CompiledLogic>, Box<CompiledLogic>), // string, separator
101    StringFormat(
102        Box<CompiledLogic>,
103        Option<Box<CompiledLogic>>,
104        Option<Box<CompiledLogic>>,
105        Option<Box<CompiledLogic>>,
106        Option<Box<CompiledLogic>>,
107    ), // value, decimals, prefix, suffix, thousands_sep
108
109    // Custom operators - Logical
110    Xor(Box<CompiledLogic>, Box<CompiledLogic>),
111    IfNull(Box<CompiledLogic>, Box<CompiledLogic>),
112    IsEmpty(Box<CompiledLogic>),
113    Empty,
114
115    // Custom operators - Date
116    Today,
117    Now,
118    Days(Box<CompiledLogic>, Box<CompiledLogic>), // end_date, start_date
119    Year(Box<CompiledLogic>),
120    Month(Box<CompiledLogic>),
121    Day(Box<CompiledLogic>),
122    Date(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // year, month, day
123    DateFormat(Box<CompiledLogic>, Option<Box<CompiledLogic>>),       // date, format
124
125    // Custom operators - Array/Table
126    Sum(
127        Box<CompiledLogic>,
128        Option<Box<CompiledLogic>>,
129        Option<Box<CompiledLogic>>,
130    ), // array/value, optional field name, optional index threshold
131    For(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // start, end, logic (with $iteration variable)
132
133    // Complex table operations
134    ValueAt(
135        Box<CompiledLogic>,
136        Box<CompiledLogic>,
137        Option<Box<CompiledLogic>>,
138    ), // table, row_idx, col_name
139    MaxAt(Box<CompiledLogic>, Box<CompiledLogic>), // table, col_name
140    IndexAt(
141        Box<CompiledLogic>,
142        Box<CompiledLogic>,
143        Box<CompiledLogic>,
144        Option<Box<CompiledLogic>>,
145    ), // lookup_val, table, field, range
146    Match(Box<CompiledLogic>, Vec<CompiledLogic>), // table, conditions (pairs of value, field)
147    MatchRange(Box<CompiledLogic>, Vec<CompiledLogic>), // table, conditions (triplets of min_col, max_col, value)
148    Choose(Box<CompiledLogic>, Vec<CompiledLogic>),     // table, conditions (pairs of value, field)
149    FindIndex(Box<CompiledLogic>, Vec<CompiledLogic>),  // table, conditions (complex nested logic)
150
151    // Array operations
152    Multiplies(Vec<CompiledLogic>), // flatten and multiply
153    Divides(Vec<CompiledLogic>),    // flatten and divide
154
155    // Advanced date functions
156    YearFrac(
157        Box<CompiledLogic>,
158        Box<CompiledLogic>,
159        Option<Box<CompiledLogic>>,
160    ), // start_date, end_date, basis
161    DateDif(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // start_date, end_date, unit
162
163    // UI helpers
164    RangeOptions(Box<CompiledLogic>, Box<CompiledLogic>), // min, max
165    MapOptions(Box<CompiledLogic>, Box<CompiledLogic>, Box<CompiledLogic>), // table, field_label, field_value
166    MapOptionsIf(
167        Box<CompiledLogic>,
168        Box<CompiledLogic>,
169        Box<CompiledLogic>,
170        Vec<CompiledLogic>,
171    ), // table, field_label, field_value, conditions
172    Return(Box<Value>), // return value as-is (no-op, just returns the raw value)
173}
174
175impl CompiledLogic {
176    /// Compile a JSON Logic expression from JSON Value
177    pub fn compile(logic: &Value) -> Result<Self, String> {
178        match logic {
179            Value::Null => Ok(CompiledLogic::Null),
180            Value::Bool(b) => Ok(CompiledLogic::Bool(*b)),
181            Value::Number(n) => {
182                Ok(CompiledLogic::Number(n.as_f64().unwrap_or(0.0)))
183            }
184            Value::String(s) => Ok(CompiledLogic::String(s.clone())),
185            Value::Array(arr) => {
186                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
187                Ok(CompiledLogic::Array(compiled?))
188            }
189            Value::Object(obj) => {
190                if obj.is_empty() {
191                    return Ok(CompiledLogic::Null);
192                }
193
194                // Get the operator (first key)
195                let (op, args) = obj.iter().next().unwrap();
196
197                Self::compile_operator(op, args)
198            }
199        }
200    }
201
202    fn compile_operator(op: &str, args: &Value) -> Result<Self, String> {
203        match op {
204            // Variable access
205            "var" => {
206                if let Value::String(name) = args {
207                    // OPTIMIZED: Pre-normalize path during compilation
208                    let normalized = path_utils::normalize_to_json_pointer(name).into_owned();
209                    Ok(CompiledLogic::Var(normalized, None))
210                } else if let Value::Array(arr) = args {
211                    if arr.is_empty() {
212                        return Err("var requires at least one argument".to_string());
213                    }
214                    let name = arr[0].as_str().ok_or("var name must be a string")?;
215                    // OPTIMIZED: Pre-normalize path during compilation
216                    let normalized = path_utils::normalize_to_json_pointer(name).into_owned();
217                    let default = if arr.len() > 1 {
218                        Some(Box::new(Self::compile(&arr[1])?))
219                    } else {
220                        None
221                    };
222                    Ok(CompiledLogic::Var(normalized, default))
223                } else {
224                    Err("var requires string or array".to_string())
225                }
226            }
227            "$ref" | "ref" => {
228                if let Value::String(path) = args {
229                    // OPTIMIZED: Pre-normalize path during compilation
230                    let normalized = path_utils::normalize_to_json_pointer(path).into_owned();
231                    Ok(CompiledLogic::Ref(normalized, None))
232                } else if let Value::Array(arr) = args {
233                    if arr.is_empty() {
234                        return Err("$ref requires at least one argument".to_string());
235                    }
236                    let path = arr[0].as_str().ok_or("$ref path must be a string")?;
237                    // OPTIMIZED: Pre-normalize path during compilation
238                    let normalized = path_utils::normalize_to_json_pointer(path).into_owned();
239                    let default = if arr.len() > 1 {
240                        Some(Box::new(Self::compile(&arr[1])?))
241                    } else {
242                        None
243                    };
244                    Ok(CompiledLogic::Ref(normalized, default))
245                } else {
246                    Err("$ref requires string or array".to_string())
247                }
248            }
249
250            // Logical operators
251            "and" => {
252                let arr = args.as_array().ok_or("and requires array")?;
253                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
254                let items = compiled?;
255                // OPTIMIZATION: Flatten nested and operations (And(And(a,b),c) -> And(a,b,c))
256                Ok(CompiledLogic::And(Self::flatten_and(items)))
257            }
258            "or" => {
259                let arr = args.as_array().ok_or("or requires array")?;
260                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
261                let items = compiled?;
262                // OPTIMIZATION: Flatten nested or operations (Or(Or(a,b),c) -> Or(a,b,c))
263                Ok(CompiledLogic::Or(Self::flatten_or(items)))
264            }
265            "!" | "not" => {
266                // Handle both array format [value] and direct value format
267                let value_to_negate = if let Value::Array(arr) = args {
268                    if arr.is_empty() {
269                        return Err("! requires an argument".to_string());
270                    }
271                    &arr[0]
272                } else {
273                    args
274                };
275
276                let inner = Self::compile(value_to_negate)?;
277                // OPTIMIZATION: Eliminate double negation (!(!x) -> x)
278                if let CompiledLogic::Not(inner_expr) = inner {
279                    Ok(*inner_expr)
280                } else {
281                    Ok(CompiledLogic::Not(Box::new(inner)))
282                }
283            }
284            "if" => {
285                let arr = args.as_array().ok_or("if requires array")?;
286                if arr.len() < 3 {
287                    return Err("if requires at least 3 arguments".to_string());
288                }
289                Ok(CompiledLogic::If(
290                    Box::new(Self::compile(&arr[0])?),
291                    Box::new(Self::compile(&arr[1])?),
292                    Box::new(Self::compile(&arr[2])?),
293                ))
294            }
295
296            // Comparison operators
297            "==" => Self::compile_binary(args, |a, b| CompiledLogic::Equal(a, b)),
298            "===" => Self::compile_binary(args, |a, b| CompiledLogic::StrictEqual(a, b)),
299            "!=" => Self::compile_binary(args, |a, b| CompiledLogic::NotEqual(a, b)),
300            "!==" => Self::compile_binary(args, |a, b| CompiledLogic::StrictNotEqual(a, b)),
301            "<" => Self::compile_binary(args, |a, b| CompiledLogic::LessThan(a, b)),
302            "<=" => Self::compile_binary(args, |a, b| CompiledLogic::LessThanOrEqual(a, b)),
303            ">" => Self::compile_binary(args, |a, b| CompiledLogic::GreaterThan(a, b)),
304            ">=" => Self::compile_binary(args, |a, b| CompiledLogic::GreaterThanOrEqual(a, b)),
305
306            // Arithmetic operators
307            "+" => {
308                let arr = args.as_array().ok_or("+ requires array")?;
309                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
310                let items = compiled?;
311                // OPTIMIZATION: Flatten nested additions (Add(Add(a,b),c) -> Add(a,b,c))
312                Ok(CompiledLogic::Add(Self::flatten_add(items)))
313            }
314            "-" => {
315                let arr = args.as_array().ok_or("- requires array")?;
316                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
317                Ok(CompiledLogic::Subtract(compiled?))
318            }
319            "*" => {
320                let arr = args.as_array().ok_or("* requires array")?;
321                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
322                let items = compiled?;
323                // OPTIMIZATION: Flatten nested multiplications
324                Ok(CompiledLogic::Multiply(Self::flatten_multiply(items)))
325            }
326            "/" => {
327                let arr = args.as_array().ok_or("/ requires array")?;
328                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
329                Ok(CompiledLogic::Divide(compiled?))
330            }
331            "%" => Self::compile_binary(args, |a, b| CompiledLogic::Modulo(a, b)),
332            "^" => Self::compile_binary(args, |a, b| CompiledLogic::Power(a, b)),
333
334            // Array operators
335            "map" => Self::compile_binary(args, |a, b| CompiledLogic::Map(a, b)),
336            "filter" => Self::compile_binary(args, |a, b| CompiledLogic::Filter(a, b)),
337            "reduce" => {
338                let arr = args.as_array().ok_or("reduce requires array")?;
339                if arr.len() < 3 {
340                    return Err("reduce requires 3 arguments".to_string());
341                }
342                Ok(CompiledLogic::Reduce(
343                    Box::new(Self::compile(&arr[0])?),
344                    Box::new(Self::compile(&arr[1])?),
345                    Box::new(Self::compile(&arr[2])?),
346                ))
347            }
348            "all" => Self::compile_binary(args, |a, b| CompiledLogic::All(a, b)),
349            "some" => Self::compile_binary(args, |a, b| CompiledLogic::Some(a, b)),
350            "none" => Self::compile_binary(args, |a, b| CompiledLogic::None(a, b)),
351            "merge" => {
352                let arr = args.as_array().ok_or("merge requires array")?;
353                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
354                Ok(CompiledLogic::Merge(compiled?))
355            }
356            "in" => Self::compile_binary(args, |a, b| CompiledLogic::In(a, b)),
357
358            // String operators
359            "cat" => {
360                let arr = args.as_array().ok_or("cat requires array")?;
361                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
362                let items = compiled?;
363                // OPTIMIZATION: Flatten nested concatenations (Cat(Cat(a,b),c) -> Cat(a,b,c))
364                Ok(CompiledLogic::Cat(Self::flatten_cat(items)))
365            }
366            "substr" => {
367                let arr = args.as_array().ok_or("substr requires array")?;
368                if arr.len() < 2 {
369                    return Err("substr requires at least 2 arguments".to_string());
370                }
371                let length = if arr.len() > 2 {
372                    Some(Box::new(Self::compile(&arr[2])?))
373                } else {
374                    None
375                };
376                Ok(CompiledLogic::Substr(
377                    Box::new(Self::compile(&arr[0])?),
378                    Box::new(Self::compile(&arr[1])?),
379                    length,
380                ))
381            }
382
383            // Utility operators
384            "missing" => {
385                let keys = if let Value::Array(arr) = args {
386                    arr.iter()
387                        .map(|v| {
388                            v.as_str()
389                                .ok_or("missing key must be string")
390                                .map(|s| s.to_string())
391                        })
392                        .collect::<Result<Vec<_>, _>>()?
393                } else if let Value::String(s) = args {
394                    vec![s.clone()]
395                } else {
396                    return Err("missing requires string or array".to_string());
397                };
398                Ok(CompiledLogic::Missing(keys))
399            }
400            "missing_some" => {
401                let arr = args.as_array().ok_or("missing_some requires array")?;
402                if arr.len() < 2 {
403                    return Err("missing_some requires at least 2 arguments".to_string());
404                }
405                let minimum = Box::new(Self::compile(&arr[0])?);
406                let keys = if let Value::Array(key_arr) = &arr[1] {
407                    key_arr
408                        .iter()
409                        .map(|v| {
410                            v.as_str()
411                                .ok_or("key must be string")
412                                .map(|s| s.to_string())
413                        })
414                        .collect::<Result<Vec<_>, _>>()?
415                } else {
416                    return Err("missing_some keys must be array".to_string());
417                };
418                Ok(CompiledLogic::MissingSome(minimum, keys))
419            }
420
421            // Custom operators - Math
422            "abs" => {
423                let arg = if let Value::Array(arr) = args {
424                    if arr.is_empty() {
425                        return Err("abs requires at least one argument".to_string());
426                    }
427                    &arr[0]
428                } else {
429                    args
430                };
431                Ok(CompiledLogic::Abs(Box::new(Self::compile(arg)?)))
432            }
433            "max" => {
434                let arr = args.as_array().ok_or("max requires array")?;
435                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
436                Ok(CompiledLogic::Max(compiled?))
437            }
438            "min" => {
439                let arr = args.as_array().ok_or("min requires array")?;
440                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
441                Ok(CompiledLogic::Min(compiled?))
442            }
443            "pow" | "**" => Self::compile_binary(args, |a, b| CompiledLogic::Pow(a, b)),
444            "round" | "ROUND" => {
445                if let Value::Array(arr) = args {
446                    let decimals = if arr.len() > 1 {
447                        Some(Box::new(Self::compile(&arr[1])?))
448                    } else {
449                        None
450                    };
451                    Ok(CompiledLogic::Round(
452                        Box::new(Self::compile(&arr[0])?),
453                        decimals,
454                    ))
455                } else {
456                    Ok(CompiledLogic::Round(Box::new(Self::compile(args)?), None))
457                }
458            }
459            "roundup" | "ROUNDUP" => {
460                if let Value::Array(arr) = args {
461                    let decimals = if arr.len() > 1 {
462                        Some(Box::new(Self::compile(&arr[1])?))
463                    } else {
464                        None
465                    };
466                    Ok(CompiledLogic::RoundUp(
467                        Box::new(Self::compile(&arr[0])?),
468                        decimals,
469                    ))
470                } else {
471                    Ok(CompiledLogic::RoundUp(Box::new(Self::compile(args)?), None))
472                }
473            }
474            "rounddown" | "ROUNDDOWN" => {
475                if let Value::Array(arr) = args {
476                    let decimals = if arr.len() > 1 {
477                        Some(Box::new(Self::compile(&arr[1])?))
478                    } else {
479                        None
480                    };
481                    Ok(CompiledLogic::RoundDown(
482                        Box::new(Self::compile(&arr[0])?),
483                        decimals,
484                    ))
485                } else {
486                    Ok(CompiledLogic::RoundDown(
487                        Box::new(Self::compile(args)?),
488                        None,
489                    ))
490                }
491            }
492            "ceiling" | "CEILING" => {
493                if let Value::Array(arr) = args {
494                    let significance = if arr.len() > 1 {
495                        Some(Box::new(Self::compile(&arr[1])?))
496                    } else {
497                        None
498                    };
499                    Ok(CompiledLogic::Ceiling(
500                        Box::new(Self::compile(&arr[0])?),
501                        significance,
502                    ))
503                } else {
504                    Ok(CompiledLogic::Ceiling(Box::new(Self::compile(args)?), None))
505                }
506            }
507            "floor" | "FLOOR" => {
508                if let Value::Array(arr) = args {
509                    let significance = if arr.len() > 1 {
510                        Some(Box::new(Self::compile(&arr[1])?))
511                    } else {
512                        None
513                    };
514                    Ok(CompiledLogic::Floor(
515                        Box::new(Self::compile(&arr[0])?),
516                        significance,
517                    ))
518                } else {
519                    Ok(CompiledLogic::Floor(Box::new(Self::compile(args)?), None))
520                }
521            }
522            "trunc" | "TRUNC" => {
523                if let Value::Array(arr) = args {
524                    let decimals = if arr.len() > 1 {
525                        Some(Box::new(Self::compile(&arr[1])?))
526                    } else {
527                        None
528                    };
529                    Ok(CompiledLogic::Trunc(
530                        Box::new(Self::compile(&arr[0])?),
531                        decimals,
532                    ))
533                } else {
534                    Ok(CompiledLogic::Trunc(Box::new(Self::compile(args)?), None))
535                }
536            }
537            "mround" | "MROUND" => Self::compile_binary(args, |a, b| CompiledLogic::Mround(a, b)),
538
539            // Custom operators - String
540            "length" => {
541                let arg = if let Value::Array(arr) = args {
542                    if arr.is_empty() {
543                        return Err("length requires at least one argument".to_string());
544                    }
545                    &arr[0]
546                } else {
547                    args
548                };
549                Ok(CompiledLogic::Length(Box::new(Self::compile(arg)?)))
550            }
551            "len" | "LEN" => {
552                let arg = if let Value::Array(arr) = args {
553                    if arr.is_empty() {
554                        return Err("len requires at least one argument".to_string());
555                    }
556                    &arr[0]
557                } else {
558                    args
559                };
560                Ok(CompiledLogic::Len(Box::new(Self::compile(arg)?)))
561            }
562            "search" | "SEARCH" => {
563                let arr = args.as_array().ok_or("search requires array")?;
564                if arr.len() < 2 {
565                    return Err("search requires at least 2 arguments".to_string());
566                }
567                let start_num = if arr.len() > 2 {
568                    Some(Box::new(Self::compile(&arr[2])?))
569                } else {
570                    None
571                };
572                Ok(CompiledLogic::Search(
573                    Box::new(Self::compile(&arr[0])?),
574                    Box::new(Self::compile(&arr[1])?),
575                    start_num,
576                ))
577            }
578            "left" | "LEFT" => {
579                if let Value::Array(arr) = args {
580                    let num_chars = if arr.len() > 1 {
581                        Some(Box::new(Self::compile(&arr[1])?))
582                    } else {
583                        None
584                    };
585                    Ok(CompiledLogic::Left(
586                        Box::new(Self::compile(&arr[0])?),
587                        num_chars,
588                    ))
589                } else {
590                    Ok(CompiledLogic::Left(Box::new(Self::compile(args)?), None))
591                }
592            }
593            "right" | "RIGHT" => {
594                if let Value::Array(arr) = args {
595                    let num_chars = if arr.len() > 1 {
596                        Some(Box::new(Self::compile(&arr[1])?))
597                    } else {
598                        None
599                    };
600                    Ok(CompiledLogic::Right(
601                        Box::new(Self::compile(&arr[0])?),
602                        num_chars,
603                    ))
604                } else {
605                    Ok(CompiledLogic::Right(Box::new(Self::compile(args)?), None))
606                }
607            }
608            "mid" | "MID" => {
609                let arr = args.as_array().ok_or("mid requires array")?;
610                if arr.len() < 3 {
611                    return Err("mid requires 3 arguments".to_string());
612                }
613                Ok(CompiledLogic::Mid(
614                    Box::new(Self::compile(&arr[0])?),
615                    Box::new(Self::compile(&arr[1])?),
616                    Box::new(Self::compile(&arr[2])?),
617                ))
618            }
619            "splittext" | "SPLITTEXT" => {
620                let arr = args.as_array().ok_or("splittext requires array")?;
621                if arr.len() < 2 {
622                    return Err("splittext requires at least 2 arguments".to_string());
623                }
624                let index = if arr.len() > 2 {
625                    Some(Box::new(Self::compile(&arr[2])?))
626                } else {
627                    None
628                };
629                Ok(CompiledLogic::SplitText(
630                    Box::new(Self::compile(&arr[0])?),
631                    Box::new(Self::compile(&arr[1])?),
632                    index,
633                ))
634            }
635            "concat" | "CONCAT" => {
636                let arr = args.as_array().ok_or("concat requires array")?;
637                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
638                Ok(CompiledLogic::Concat(compiled?))
639            }
640            "splitvalue" | "SPLITVALUE" => {
641                Self::compile_binary(args, |a, b| CompiledLogic::SplitValue(a, b))
642            }
643            "stringformat" | "STRINGFORMAT" => {
644                let arr = args.as_array().ok_or("stringformat requires array")?;
645                if arr.is_empty() {
646                    return Err("stringformat requires at least 1 argument".to_string());
647                }
648                let decimals = if arr.len() > 1 {
649                    Some(Box::new(Self::compile(&arr[1])?))
650                } else {
651                    None
652                };
653                let prefix = if arr.len() > 2 {
654                    Some(Box::new(Self::compile(&arr[2])?))
655                } else {
656                    None
657                };
658                let suffix = if arr.len() > 3 {
659                    Some(Box::new(Self::compile(&arr[3])?))
660                } else {
661                    None
662                };
663                let thousands_sep = if arr.len() > 4 {
664                    Some(Box::new(Self::compile(&arr[4])?))
665                } else {
666                    None
667                };
668                Ok(CompiledLogic::StringFormat(
669                    Box::new(Self::compile(&arr[0])?),
670                    decimals,
671                    prefix,
672                    suffix,
673                    thousands_sep,
674                ))
675            }
676
677            // Custom operators - Logical
678            "xor" => Self::compile_binary(args, |a, b| CompiledLogic::Xor(a, b)),
679            "ifnull" | "IFNULL" => Self::compile_binary(args, |a, b| CompiledLogic::IfNull(a, b)),
680            "isempty" | "ISEMPTY" => {
681                let arg = if let Value::Array(arr) = args {
682                    if arr.is_empty() {
683                        return Err("ISEMPTY requires at least one argument".to_string());
684                    }
685                    &arr[0]
686                } else {
687                    args
688                };
689                Ok(CompiledLogic::IsEmpty(Box::new(Self::compile(arg)?)))
690            }
691            "empty" | "EMPTY" => Ok(CompiledLogic::Empty),
692
693            // Custom operators - Date
694            "today" | "TODAY" => Ok(CompiledLogic::Today),
695            "now" | "NOW" => Ok(CompiledLogic::Now),
696            "days" | "DAYS" => Self::compile_binary(args, |a, b| CompiledLogic::Days(a, b)),
697            "year" | "YEAR" => {
698                let arg = if let Value::Array(arr) = args {
699                    if arr.is_empty() {
700                        return Err("year requires at least one argument".to_string());
701                    }
702                    &arr[0]
703                } else {
704                    args
705                };
706                Ok(CompiledLogic::Year(Box::new(Self::compile(arg)?)))
707            }
708            "month" | "MONTH" => {
709                let arg = if let Value::Array(arr) = args {
710                    if arr.is_empty() {
711                        return Err("month requires at least one argument".to_string());
712                    }
713                    &arr[0]
714                } else {
715                    args
716                };
717                Ok(CompiledLogic::Month(Box::new(Self::compile(arg)?)))
718            }
719            "day" | "DAY" => {
720                let arg = if let Value::Array(arr) = args {
721                    if arr.is_empty() {
722                        return Err("day requires at least one argument".to_string());
723                    }
724                    &arr[0]
725                } else {
726                    args
727                };
728                Ok(CompiledLogic::Day(Box::new(Self::compile(arg)?)))
729            }
730            "date" | "DATE" => {
731                let arr = args.as_array().ok_or("date requires array")?;
732                if arr.len() < 3 {
733                    return Err("date requires 3 arguments".to_string());
734                }
735                Ok(CompiledLogic::Date(
736                    Box::new(Self::compile(&arr[0])?),
737                    Box::new(Self::compile(&arr[1])?),
738                    Box::new(Self::compile(&arr[2])?),
739                ))
740            }
741            "dateformat" | "DATEFORMAT" => {
742                if let Value::Array(arr) = args {
743                    if arr.is_empty() {
744                        return Err("dateformat requires at least 1 argument".to_string());
745                    }
746                    let format = if arr.len() > 1 {
747                        Some(Box::new(Self::compile(&arr[1])?))
748                    } else {
749                        None
750                    };
751                    Ok(CompiledLogic::DateFormat(
752                        Box::new(Self::compile(&arr[0])?),
753                        format,
754                    ))
755                } else {
756                    Ok(CompiledLogic::DateFormat(
757                        Box::new(Self::compile(args)?),
758                        None,
759                    ))
760                }
761            }
762
763            // Custom operators - Array/Table
764            "sum" | "SUM" => {
765                if let Value::Array(arr) = args {
766                    if arr.is_empty() {
767                        return Err("sum requires at least 1 argument".to_string());
768                    }
769                    let field = if arr.len() > 1 {
770                        Some(Box::new(Self::compile(&arr[1])?))
771                    } else {
772                        None
773                    };
774                    let threshold = if arr.len() > 2 {
775                        Some(Box::new(Self::compile(&arr[2])?))
776                    } else {
777                        None
778                    };
779                    Ok(CompiledLogic::Sum(
780                        Box::new(Self::compile(&arr[0])?),
781                        field,
782                        threshold,
783                    ))
784                } else {
785                    Ok(CompiledLogic::Sum(
786                        Box::new(Self::compile(args)?),
787                        None,
788                        None,
789                    ))
790                }
791            }
792            "FOR" => {
793                let arr = args.as_array().ok_or("FOR requires array")?;
794                if arr.len() < 3 {
795                    return Err("FOR requires 3 arguments: start, end, logic".to_string());
796                }
797                Ok(CompiledLogic::For(
798                    Box::new(Self::compile(&arr[0])?),
799                    Box::new(Self::compile(&arr[1])?),
800                    Box::new(Self::compile(&arr[2])?),
801                ))
802            }
803
804            // Complex table operations
805            "valueat" | "VALUEAT" => {
806                let arr = args.as_array().ok_or("VALUEAT requires array")?;
807                if arr.len() < 2 {
808                    return Err("VALUEAT requires at least 2 arguments".to_string());
809                }
810                let col_name = if arr.len() > 2 {
811                    Some(Box::new(Self::compile(&arr[2])?))
812                } else {
813                    None
814                };
815                Ok(CompiledLogic::ValueAt(
816                    Box::new(Self::compile(&arr[0])?),
817                    Box::new(Self::compile(&arr[1])?),
818                    col_name,
819                ))
820            }
821            "maxat" | "MAXAT" => Self::compile_binary(args, |a, b| CompiledLogic::MaxAt(a, b)),
822            "indexat" | "INDEXAT" => {
823                let arr = args.as_array().ok_or("INDEXAT requires array")?;
824                if arr.len() < 3 {
825                    return Err("INDEXAT requires at least 3 arguments".to_string());
826                }
827                let range = if arr.len() > 3 {
828                    Some(Box::new(Self::compile(&arr[3])?))
829                } else {
830                    None
831                };
832                Ok(CompiledLogic::IndexAt(
833                    Box::new(Self::compile(&arr[0])?),
834                    Box::new(Self::compile(&arr[1])?),
835                    Box::new(Self::compile(&arr[2])?),
836                    range,
837                ))
838            }
839            "match" | "MATCH" => {
840                let arr = args.as_array().ok_or("MATCH requires array")?;
841                if arr.is_empty() {
842                    return Err("MATCH requires at least 1 argument".to_string());
843                }
844                let table = Box::new(Self::compile(&arr[0])?);
845                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
846                Ok(CompiledLogic::Match(table, conditions?))
847            }
848            "matchrange" | "MATCHRANGE" => {
849                let arr = args.as_array().ok_or("MATCHRANGE requires array")?;
850                if arr.is_empty() {
851                    return Err("MATCHRANGE requires at least 1 argument".to_string());
852                }
853                let table = Box::new(Self::compile(&arr[0])?);
854                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
855                Ok(CompiledLogic::MatchRange(table, conditions?))
856            }
857            "choose" | "CHOOSE" => {
858                let arr = args.as_array().ok_or("CHOOSE requires array")?;
859                if arr.is_empty() {
860                    return Err("CHOOSE requires at least 1 argument".to_string());
861                }
862                let table = Box::new(Self::compile(&arr[0])?);
863                let conditions: Result<Vec<_>, _> = arr[1..].iter().map(Self::compile).collect();
864                Ok(CompiledLogic::Choose(table, conditions?))
865            }
866            "findindex" | "FINDINDEX" => {
867                let arr = args.as_array().ok_or("FINDINDEX requires array")?;
868                if arr.len() < 2 {
869                    return Err("FINDINDEX requires at least 2 arguments".to_string());
870                }
871                let table = Box::new(Self::compile(&arr[0])?);
872                // CRITICAL: Convert string literals to var references in conditions
873                // This allows ergonomic syntax: "INSAGE" instead of {"var": "INSAGE"}
874                let conditions: Result<Vec<_>, _> = arr[1..]
875                    .iter()
876                    .map(|cond| Self::compile(&Self::preprocess_table_condition(cond)))
877                    .collect();
878                Ok(CompiledLogic::FindIndex(table, conditions?))
879            }
880
881            // Array operations
882            "MULTIPLIES" => {
883                let arr = args.as_array().ok_or("MULTIPLIES requires array")?;
884                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
885                Ok(CompiledLogic::Multiplies(compiled?))
886            }
887            "DIVIDES" => {
888                let arr = args.as_array().ok_or("DIVIDES requires array")?;
889                let compiled: Result<Vec<_>, _> = arr.iter().map(Self::compile).collect();
890                Ok(CompiledLogic::Divides(compiled?))
891            }
892
893            // Advanced date functions
894            "YEARFRAC" => {
895                let arr = args.as_array().ok_or("YEARFRAC requires array")?;
896                if arr.len() < 2 {
897                    return Err("YEARFRAC requires at least 2 arguments".to_string());
898                }
899                let basis = if arr.len() > 2 {
900                    Some(Box::new(Self::compile(&arr[2])?))
901                } else {
902                    None
903                };
904                Ok(CompiledLogic::YearFrac(
905                    Box::new(Self::compile(&arr[0])?),
906                    Box::new(Self::compile(&arr[1])?),
907                    basis,
908                ))
909            }
910            "DATEDIF" => {
911                let arr = args.as_array().ok_or("DATEDIF requires array")?;
912                if arr.len() < 3 {
913                    return Err("DATEDIF requires 3 arguments".to_string());
914                }
915                Ok(CompiledLogic::DateDif(
916                    Box::new(Self::compile(&arr[0])?),
917                    Box::new(Self::compile(&arr[1])?),
918                    Box::new(Self::compile(&arr[2])?),
919                ))
920            }
921
922            // UI helpers
923            "RANGEOPTIONS" => Self::compile_binary(args, |a, b| CompiledLogic::RangeOptions(a, b)),
924            "MAPOPTIONS" => {
925                let arr = args.as_array().ok_or("MAPOPTIONS requires array")?;
926                if arr.len() < 3 {
927                    return Err("MAPOPTIONS requires 3 arguments".to_string());
928                }
929                Ok(CompiledLogic::MapOptions(
930                    Box::new(Self::compile(&arr[0])?),
931                    Box::new(Self::compile(&arr[1])?),
932                    Box::new(Self::compile(&arr[2])?),
933                ))
934            }
935            "MAPOPTIONSIF" => {
936                let arr = args.as_array().ok_or("MAPOPTIONSIF requires array")?;
937                if arr.len() < 4 {
938                    return Err("MAPOPTIONSIF requires at least 4 arguments".to_string());
939                }
940                let table = Box::new(Self::compile(&arr[0])?);
941                let field_label = Box::new(Self::compile(&arr[1])?);
942                let field_value = Box::new(Self::compile(&arr[2])?);
943
944                // Handle triplet syntax: value, operator, field -> {operator: [value, {var: field}]}
945                let condition_args = &arr[3..];
946                let mut conditions = Vec::new();
947
948                let mut i = 0;
949                while i + 2 < condition_args.len() {
950                    let value = &condition_args[i];
951                    let operator = &condition_args[i + 1];
952                    let field = &condition_args[i + 2];
953
954                    if let Value::String(op) = operator {
955                        // Create comparison: {op: [value, {var: field}]}
956                        let field_var = if let Value::String(f) = field {
957                            serde_json::json!({"var": f})
958                        } else {
959                            field.clone()
960                        };
961
962                        let comparison = serde_json::json!({
963                            op: [value.clone(), field_var]
964                        });
965
966                        conditions.push(Self::compile(&comparison)?);
967                    }
968
969                    i += 3;
970                }
971
972                // Handle any remaining individual conditions
973                while i < condition_args.len() {
974                    conditions.push(Self::compile(&Self::preprocess_table_condition(
975                        &condition_args[i],
976                    ))?);
977                    i += 1;
978                }
979
980                Ok(CompiledLogic::MapOptionsIf(
981                    table,
982                    field_label,
983                    field_value,
984                    conditions,
985                ))
986            }
987            "return" => Ok(CompiledLogic::Return(Box::new(args.clone()))),
988
989            _ => Err(format!("Unknown operator: {}", op)),
990        }
991    }
992
993    fn compile_binary<F>(args: &Value, f: F) -> Result<Self, String>
994    where
995        F: FnOnce(Box<CompiledLogic>, Box<CompiledLogic>) -> CompiledLogic,
996    {
997        let arr = args.as_array().ok_or("Binary operator requires array")?;
998        if arr.len() != 2 {
999            return Err("Binary operator requires exactly 2 arguments".to_string());
1000        }
1001        Ok(f(
1002            Box::new(Self::compile(&arr[0])?),
1003            Box::new(Self::compile(&arr[1])?),
1004        ))
1005    }
1006
1007    /// Preprocess table condition to convert string literals to var references
1008    /// This allows ergonomic syntax in FINDINDEX/MATCH/CHOOSE conditions
1009    ///
1010    /// Handles formats:
1011    /// - Comparison triplets: ["==", value, "col"] -> {"==": [value, {"var": "col"}]}
1012    /// - Logical operators: ["&&", cond1, cond2] -> {"and": [cond1, cond2]}
1013    /// - String field names: "col" -> {"var": "col"}
1014    fn preprocess_table_condition(value: &Value) -> Value {
1015        match value {
1016            Value::String(s) => {
1017                // Convert standalone strings to var references
1018                serde_json::json!({"var": s})
1019            }
1020            Value::Array(arr) => {
1021                // Check if this is an operator in array shorthand format
1022                if !arr.is_empty() {
1023                    if let Some(op_str) = arr[0].as_str() {
1024                        // Check for comparison operators: [op, value, col]
1025                        let is_comparison = matches!(
1026                            op_str,
1027                            "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">="
1028                        );
1029
1030                        if is_comparison && arr.len() >= 3 {
1031                            // Comparison triplet: [op, value, col] -> {op: [col_var, value]}
1032                            // Evaluates as: row[col] op value
1033                            // DON'T preprocess the value (2nd arg) - keep it as-is
1034                            let value_arg = arr[1].clone();
1035
1036                            // Only convert the column name (3rd arg) to var reference if it's a string
1037                            let col_arg = if let Value::String(col) = &arr[2] {
1038                                // Convert column name string to var reference
1039                                serde_json::json!({"var": col})
1040                            } else {
1041                                // If it's not a string, preprocess it (could be nested expression)
1042                                Self::preprocess_table_condition(&arr[2])
1043                            };
1044
1045                            // Order matters: {op: [col_var, value]} means row[col] op value
1046                            let mut obj = serde_json::Map::new();
1047                            obj.insert(op_str.to_string(), Value::Array(vec![col_arg, value_arg]));
1048                            return Value::Object(obj);
1049                        }
1050
1051                        // Check for logical operators: [op, arg1, arg2, ...]
1052                        let canonical_op = match op_str {
1053                            "&&" => Some("and"),
1054                            "||" => Some("or"),
1055                            "and" | "or" | "!" | "not" | "if" => Some(op_str),
1056                            _ => None,
1057                        };
1058
1059                        if let Some(op_name) = canonical_op {
1060                            // Convert ["op", arg1, arg2, ...] to {"op": [arg1, arg2, ...]}
1061                            let args: Vec<Value> = arr[1..]
1062                                .iter()
1063                                .map(Self::preprocess_table_condition)
1064                                .collect();
1065                            let mut obj = serde_json::Map::new();
1066                            obj.insert(op_name.to_string(), Value::Array(args));
1067                            return Value::Object(obj);
1068                        }
1069                    }
1070                }
1071                // Regular array - recursively process elements
1072                Value::Array(arr.iter().map(Self::preprocess_table_condition).collect())
1073            }
1074            Value::Object(obj) => {
1075                // Recursively process object values, but preserve operators
1076                let mut new_obj = serde_json::Map::new();
1077                for (key, val) in obj {
1078                    // Don't convert strings inside $ref, var, or other special operators
1079                    if key == "$ref" || key == "ref" || key == "var" {
1080                        new_obj.insert(key.clone(), val.clone());
1081                    } else {
1082                        new_obj.insert(key.clone(), Self::preprocess_table_condition(val));
1083                    }
1084                }
1085                Value::Object(new_obj)
1086            }
1087            _ => value.clone(),
1088        }
1089    }
1090
1091    /// Check if this is a simple reference that doesn't need caching
1092    pub fn is_simple_ref(&self) -> bool {
1093        matches!(
1094            self,
1095            CompiledLogic::Ref(_, None) | CompiledLogic::Var(_, None)
1096        )
1097    }
1098
1099    /// Extract all variable names referenced in this logic
1100    pub fn referenced_vars(&self) -> Vec<String> {
1101        let mut vars = Vec::new();
1102        self.collect_vars(&mut vars);
1103        vars.sort();
1104        vars.dedup();
1105        vars
1106    }
1107
1108    /// Flatten nested And operations only
1109    fn flatten_and(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1110        let mut flattened = Vec::new();
1111        for item in items {
1112            match item {
1113                CompiledLogic::And(nested) => {
1114                    // Recursively flatten nested And operations
1115                    flattened.extend(Self::flatten_and(nested));
1116                }
1117                _ => flattened.push(item),
1118            }
1119        }
1120        flattened
1121    }
1122
1123    /// Flatten nested Or operations only
1124    fn flatten_or(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1125        let mut flattened = Vec::new();
1126        for item in items {
1127            match item {
1128                CompiledLogic::Or(nested) => {
1129                    // Recursively flatten nested Or operations
1130                    flattened.extend(Self::flatten_or(nested));
1131                }
1132                _ => flattened.push(item),
1133            }
1134        }
1135        flattened
1136    }
1137
1138    /// Flatten nested Add operations only
1139    fn flatten_add(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1140        let mut flattened = Vec::new();
1141        for item in items {
1142            match item {
1143                CompiledLogic::Add(nested) => {
1144                    // Recursively flatten nested Adds
1145                    flattened.extend(Self::flatten_add(nested));
1146                }
1147                _ => flattened.push(item),
1148            }
1149        }
1150        flattened
1151    }
1152
1153    /// Flatten nested Multiply operations only
1154    fn flatten_multiply(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1155        let mut flattened = Vec::new();
1156        for item in items {
1157            match item {
1158                CompiledLogic::Multiply(nested) => {
1159                    // Recursively flatten nested Multiplies
1160                    flattened.extend(Self::flatten_multiply(nested));
1161                }
1162                _ => flattened.push(item),
1163            }
1164        }
1165        flattened
1166    }
1167
1168    /// Flatten nested Cat (concatenation) operations
1169    /// Combines nested Cat operations into a single flat operation
1170    fn flatten_cat(items: Vec<CompiledLogic>) -> Vec<CompiledLogic> {
1171        let mut flattened = Vec::new();
1172        for item in items {
1173            match &item {
1174                CompiledLogic::Cat(nested) => {
1175                    // Recursively flatten nested Cat operations
1176                    flattened.extend(Self::flatten_cat(nested.clone()));
1177                }
1178                _ => flattened.push(item),
1179            }
1180        }
1181        flattened
1182    }
1183
1184    /// Check if this logic contains forward references (e.g., VALUEAT with $iteration + N where N > 0)
1185    /// Returns true if it references future iterations in a table
1186    pub fn has_forward_reference(&self) -> bool {
1187        let result = self.check_forward_reference();
1188        result
1189    }
1190
1191    fn check_forward_reference(&self) -> bool {
1192        match self {
1193            // VALUEAT with $iteration arithmetic
1194            CompiledLogic::ValueAt(table, idx_logic, col_name) => {
1195                // Check if index contains $iteration + positive_number
1196                let has_fwd = idx_logic.contains_iteration_plus_positive();
1197                if has_fwd {
1198                    return true;
1199                }
1200                // Recursively check all parameters
1201                let table_fwd = table.check_forward_reference();
1202                let idx_fwd = idx_logic.check_forward_reference();
1203                let col_fwd = col_name
1204                    .as_ref()
1205                    .map(|c| c.check_forward_reference())
1206                    .unwrap_or(false);
1207                table_fwd || idx_fwd || col_fwd
1208            }
1209            // Recursively check compound operations
1210            CompiledLogic::Array(arr) => arr.iter().any(|item| item.check_forward_reference()),
1211            CompiledLogic::And(items)
1212            | CompiledLogic::Or(items)
1213            | CompiledLogic::Add(items)
1214            | CompiledLogic::Subtract(items)
1215            | CompiledLogic::Multiply(items)
1216            | CompiledLogic::Divide(items)
1217            | CompiledLogic::Merge(items)
1218            | CompiledLogic::Cat(items)
1219            | CompiledLogic::Max(items)
1220            | CompiledLogic::Min(items)
1221            | CompiledLogic::Concat(items)
1222            | CompiledLogic::Multiplies(items)
1223            | CompiledLogic::Divides(items) => {
1224                items.iter().any(|item| item.check_forward_reference())
1225            }
1226            CompiledLogic::Not(a)
1227            | CompiledLogic::Abs(a)
1228            | CompiledLogic::Length(a)
1229            | CompiledLogic::Len(a)
1230            | CompiledLogic::IsEmpty(a)
1231            | CompiledLogic::Year(a)
1232            | CompiledLogic::Month(a)
1233            | CompiledLogic::Day(a) => a.check_forward_reference(),
1234            CompiledLogic::Round(a, decimals)
1235            | CompiledLogic::RoundUp(a, decimals)
1236            | CompiledLogic::RoundDown(a, decimals)
1237            | CompiledLogic::Ceiling(a, decimals)
1238            | CompiledLogic::Floor(a, decimals)
1239            | CompiledLogic::Trunc(a, decimals)
1240            | CompiledLogic::DateFormat(a, decimals) => {
1241                a.check_forward_reference()
1242                    || decimals
1243                        .as_ref()
1244                        .map_or(false, |d| d.check_forward_reference())
1245            }
1246            CompiledLogic::StringFormat(a, decimals, prefix, suffix, sep) => {
1247                a.check_forward_reference()
1248                    || decimals
1249                        .as_ref()
1250                        .map_or(false, |d| d.check_forward_reference())
1251                    || prefix
1252                        .as_ref()
1253                        .map_or(false, |p| p.check_forward_reference())
1254                    || suffix
1255                        .as_ref()
1256                        .map_or(false, |s| s.check_forward_reference())
1257                    || sep.as_ref().map_or(false, |s| s.check_forward_reference())
1258            }
1259            CompiledLogic::Mround(a, b) => {
1260                a.check_forward_reference() || b.check_forward_reference()
1261            }
1262            CompiledLogic::Return(_) => false, // Raw values don't have forward references
1263            CompiledLogic::If(cond, then_val, else_val) => {
1264                cond.check_forward_reference()
1265                    || then_val.check_forward_reference()
1266                    || else_val.check_forward_reference()
1267            }
1268            CompiledLogic::Equal(a, b)
1269            | CompiledLogic::StrictEqual(a, b)
1270            | CompiledLogic::NotEqual(a, b)
1271            | CompiledLogic::StrictNotEqual(a, b)
1272            | CompiledLogic::LessThan(a, b)
1273            | CompiledLogic::LessThanOrEqual(a, b)
1274            | CompiledLogic::GreaterThan(a, b)
1275            | CompiledLogic::GreaterThanOrEqual(a, b)
1276            | CompiledLogic::Modulo(a, b)
1277            | CompiledLogic::Power(a, b)
1278            | CompiledLogic::Map(a, b)
1279            | CompiledLogic::Filter(a, b)
1280            | CompiledLogic::All(a, b)
1281            | CompiledLogic::Some(a, b)
1282            | CompiledLogic::None(a, b)
1283            | CompiledLogic::In(a, b)
1284            | CompiledLogic::Pow(a, b)
1285            | CompiledLogic::Xor(a, b)
1286            | CompiledLogic::IfNull(a, b)
1287            | CompiledLogic::Days(a, b)
1288            | CompiledLogic::SplitValue(a, b)
1289            | CompiledLogic::MaxAt(a, b)
1290            | CompiledLogic::RangeOptions(a, b) => {
1291                a.check_forward_reference() || b.check_forward_reference()
1292            }
1293            CompiledLogic::Reduce(a, b, c)
1294            | CompiledLogic::Mid(a, b, c)
1295            | CompiledLogic::Date(a, b, c)
1296            | CompiledLogic::DateDif(a, b, c)
1297            | CompiledLogic::MapOptions(a, b, c)
1298            | CompiledLogic::For(a, b, c) => {
1299                a.check_forward_reference()
1300                    || b.check_forward_reference()
1301                    || c.check_forward_reference()
1302            }
1303            CompiledLogic::Substr(s, start, len)
1304            | CompiledLogic::Search(s, start, len)
1305            | CompiledLogic::SplitText(s, start, len)
1306            | CompiledLogic::YearFrac(s, start, len) => {
1307                s.check_forward_reference()
1308                    || start.check_forward_reference()
1309                    || len
1310                        .as_ref()
1311                        .map(|l| l.check_forward_reference())
1312                        .unwrap_or(false)
1313            }
1314            CompiledLogic::Left(a, opt) | CompiledLogic::Right(a, opt) => {
1315                a.check_forward_reference()
1316                    || opt
1317                        .as_ref()
1318                        .map(|o| o.check_forward_reference())
1319                        .unwrap_or(false)
1320            }
1321            CompiledLogic::Sum(a, opt1, opt2) => {
1322                a.check_forward_reference()
1323                    || opt1
1324                        .as_ref()
1325                        .map(|o| o.check_forward_reference())
1326                        .unwrap_or(false)
1327                    || opt2
1328                        .as_ref()
1329                        .map(|o| o.check_forward_reference())
1330                        .unwrap_or(false)
1331            }
1332            CompiledLogic::IndexAt(a, b, c, opt) => {
1333                a.check_forward_reference()
1334                    || b.check_forward_reference()
1335                    || c.check_forward_reference()
1336                    || opt
1337                        .as_ref()
1338                        .map(|o| o.check_forward_reference())
1339                        .unwrap_or(false)
1340            }
1341            CompiledLogic::Match(table, conds)
1342            | CompiledLogic::MatchRange(table, conds)
1343            | CompiledLogic::Choose(table, conds)
1344            | CompiledLogic::FindIndex(table, conds) => {
1345                table.check_forward_reference() || conds.iter().any(|c| c.check_forward_reference())
1346            }
1347            CompiledLogic::MapOptionsIf(table, label, value, conds) => {
1348                table.check_forward_reference()
1349                    || label.check_forward_reference()
1350                    || value.check_forward_reference()
1351                    || conds.iter().any(|c| c.check_forward_reference())
1352            }
1353            CompiledLogic::MissingSome(min, _) => min.check_forward_reference(),
1354            _ => false,
1355        }
1356    }
1357
1358    /// Check if logic contains $iteration + positive_number pattern
1359    fn contains_iteration_plus_positive(&self) -> bool {
1360        match self {
1361            CompiledLogic::Add(items) => {
1362                // Check if one operand references $iteration and another is a positive number literal
1363                let has_iteration = items
1364                    .iter()
1365                    .any(|item| item.referenced_vars().iter().any(|v| v == "$iteration"));
1366
1367                let has_positive = items.iter().any(|item| match item {
1368                    CompiledLogic::Number(f) => *f > 0.0,
1369                    _ => false,
1370                });
1371
1372                let result = has_iteration && has_positive;
1373                result
1374            }
1375            _ => false,
1376        }
1377    }
1378
1379    /// Normalize JSON Schema reference path to dot notation
1380    /// Handles: #/schema/path, #/properties/field, /properties/field, field.path
1381    /// Trims /properties/ and .properties. segments
1382    fn normalize_ref_path(path: &str) -> String {
1383        let mut normalized = path.to_string();
1384
1385        // Remove leading #/ if present
1386        if normalized.starts_with("#/") {
1387            normalized = normalized[2..].to_string();
1388        } else if normalized.starts_with('/') {
1389            normalized = normalized[1..].to_string();
1390        }
1391
1392        // Replace / with . for JSON pointer notation
1393        normalized = normalized.replace('/', ".");
1394
1395        // Remove /properties/ or .properties. segments
1396        normalized = normalized.replace("properties.", "");
1397        normalized = normalized.replace(".properties.", ".");
1398
1399        // Clean up any double dots
1400        while normalized.contains("..") {
1401            normalized = normalized.replace("..", ".");
1402        }
1403
1404        // Remove leading/trailing dots
1405        normalized = normalized.trim_matches('.').to_string();
1406
1407        normalized
1408    }
1409
1410    pub fn collect_vars(&self, vars: &mut Vec<String>) {
1411        match self {
1412            CompiledLogic::Var(name, default) => {
1413                vars.push(name.clone());
1414                if let Some(def) = default {
1415                    def.collect_vars(vars);
1416                }
1417            }
1418            CompiledLogic::Ref(path, default) => {
1419                // Normalize the path and add it
1420                vars.push(Self::normalize_ref_path(path));
1421                if let Some(def) = default {
1422                    def.collect_vars(vars);
1423                }
1424            }
1425            CompiledLogic::Array(arr) => {
1426                for item in arr {
1427                    item.collect_vars(vars);
1428                }
1429            }
1430            CompiledLogic::And(items)
1431            | CompiledLogic::Or(items)
1432            | CompiledLogic::Add(items)
1433            | CompiledLogic::Subtract(items)
1434            | CompiledLogic::Multiply(items)
1435            | CompiledLogic::Divide(items)
1436            | CompiledLogic::Merge(items)
1437            | CompiledLogic::Cat(items)
1438            | CompiledLogic::Max(items)
1439            | CompiledLogic::Min(items)
1440            | CompiledLogic::Concat(items) => {
1441                for item in items {
1442                    item.collect_vars(vars);
1443                }
1444            }
1445            CompiledLogic::Not(a)
1446            | CompiledLogic::Abs(a)
1447            | CompiledLogic::Length(a)
1448            | CompiledLogic::Len(a)
1449            | CompiledLogic::IsEmpty(a)
1450            | CompiledLogic::Year(a)
1451            | CompiledLogic::Month(a)
1452            | CompiledLogic::Day(a) => {
1453                a.collect_vars(vars);
1454            }
1455            CompiledLogic::Round(a, decimals)
1456            | CompiledLogic::RoundUp(a, decimals)
1457            | CompiledLogic::RoundDown(a, decimals)
1458            | CompiledLogic::Ceiling(a, decimals)
1459            | CompiledLogic::Floor(a, decimals)
1460            | CompiledLogic::Trunc(a, decimals)
1461            | CompiledLogic::DateFormat(a, decimals) => {
1462                a.collect_vars(vars);
1463                if let Some(d) = decimals {
1464                    d.collect_vars(vars);
1465                }
1466            }
1467            CompiledLogic::StringFormat(a, decimals, prefix, suffix, sep) => {
1468                a.collect_vars(vars);
1469                if let Some(d) = decimals {
1470                    d.collect_vars(vars);
1471                }
1472                if let Some(p) = prefix {
1473                    p.collect_vars(vars);
1474                }
1475                if let Some(s) = suffix {
1476                    s.collect_vars(vars);
1477                }
1478                if let Some(s) = sep {
1479                    s.collect_vars(vars);
1480                }
1481            }
1482            CompiledLogic::Mround(a, b) => {
1483                a.collect_vars(vars);
1484                b.collect_vars(vars);
1485            }
1486            CompiledLogic::Return(_) => {} // Raw values don't contain vars
1487            CompiledLogic::If(cond, then_val, else_val) => {
1488                cond.collect_vars(vars);
1489                then_val.collect_vars(vars);
1490                else_val.collect_vars(vars);
1491            }
1492            CompiledLogic::Equal(a, b)
1493            | CompiledLogic::StrictEqual(a, b)
1494            | CompiledLogic::NotEqual(a, b)
1495            | CompiledLogic::StrictNotEqual(a, b)
1496            | CompiledLogic::LessThan(a, b)
1497            | CompiledLogic::LessThanOrEqual(a, b)
1498            | CompiledLogic::GreaterThan(a, b)
1499            | CompiledLogic::GreaterThanOrEqual(a, b)
1500            | CompiledLogic::Modulo(a, b)
1501            | CompiledLogic::Power(a, b)
1502            | CompiledLogic::Map(a, b)
1503            | CompiledLogic::Filter(a, b)
1504            | CompiledLogic::All(a, b)
1505            | CompiledLogic::Some(a, b)
1506            | CompiledLogic::None(a, b)
1507            | CompiledLogic::In(a, b)
1508            | CompiledLogic::Pow(a, b)
1509            | CompiledLogic::Xor(a, b)
1510            | CompiledLogic::IfNull(a, b)
1511            | CompiledLogic::Days(a, b)
1512            | CompiledLogic::SplitValue(a, b)
1513            | CompiledLogic::MaxAt(a, b)
1514            | CompiledLogic::RangeOptions(a, b) => {
1515                a.collect_vars(vars);
1516                b.collect_vars(vars);
1517            }
1518            CompiledLogic::Reduce(a, b, c)
1519            | CompiledLogic::Mid(a, b, c)
1520            | CompiledLogic::Date(a, b, c)
1521            | CompiledLogic::DateDif(a, b, c)
1522            | CompiledLogic::MapOptions(a, b, c)
1523            | CompiledLogic::For(a, b, c) => {
1524                a.collect_vars(vars);
1525                b.collect_vars(vars);
1526                c.collect_vars(vars);
1527            }
1528            CompiledLogic::Substr(s, start, len)
1529            | CompiledLogic::Search(s, start, len)
1530            | CompiledLogic::SplitText(s, start, len)
1531            | CompiledLogic::YearFrac(s, start, len) => {
1532                s.collect_vars(vars);
1533                start.collect_vars(vars);
1534                if let Some(l) = len {
1535                    l.collect_vars(vars);
1536                }
1537            }
1538            CompiledLogic::Left(a, opt)
1539            | CompiledLogic::Right(a, opt)
1540            | CompiledLogic::ValueAt(a, _, opt) => {
1541                a.collect_vars(vars);
1542                if let Some(o) = opt {
1543                    o.collect_vars(vars);
1544                }
1545            }
1546            CompiledLogic::Sum(a, opt1, opt2) => {
1547                a.collect_vars(vars);
1548                if let Some(o) = opt1 {
1549                    o.collect_vars(vars);
1550                }
1551                if let Some(o) = opt2 {
1552                    o.collect_vars(vars);
1553                }
1554            }
1555            CompiledLogic::IndexAt(a, b, c, opt) => {
1556                a.collect_vars(vars);
1557                b.collect_vars(vars);
1558                c.collect_vars(vars);
1559                if let Some(o) = opt {
1560                    o.collect_vars(vars);
1561                }
1562            }
1563            CompiledLogic::Match(table, conds)
1564            | CompiledLogic::MatchRange(table, conds)
1565            | CompiledLogic::Choose(table, conds)
1566            | CompiledLogic::FindIndex(table, conds) => {
1567                table.collect_vars(vars);
1568                for cond in conds {
1569                    cond.collect_vars(vars);
1570                }
1571            }
1572            CompiledLogic::Multiplies(items) | CompiledLogic::Divides(items) => {
1573                for item in items {
1574                    item.collect_vars(vars);
1575                }
1576            }
1577            CompiledLogic::MapOptionsIf(table, label, value, conds) => {
1578                table.collect_vars(vars);
1579                label.collect_vars(vars);
1580                value.collect_vars(vars);
1581                for cond in conds {
1582                    cond.collect_vars(vars);
1583                }
1584            }
1585            CompiledLogic::MissingSome(min, _) => {
1586                min.collect_vars(vars);
1587            }
1588            _ => {}
1589        }
1590    }
1591}
1592
1593/// Storage for compiled logic expressions with dependency tracking
1594///
1595/// This store uses the global compiled logic cache to avoid recompiling
1596/// the same logic across different instances. Each instance maintains
1597/// its own local ID mapping to the global storage.
1598pub struct CompiledLogicStore {
1599    next_id: u64,
1600    store: AHashMap<LogicId, CompiledLogic>,
1601    dependencies: AHashMap<LogicId, Vec<String>>,
1602}
1603
1604impl CompiledLogicStore {
1605    pub fn new() -> Self {
1606        Self {
1607            next_id: 0,
1608            store: AHashMap::default(),
1609            dependencies: AHashMap::default(),
1610        }
1611    }
1612
1613    /// Compile and store a JSON Logic expression
1614    ///
1615    /// Uses global storage to avoid recompiling the same logic across instances.
1616    /// The logic is compiled once globally and reused, with this instance maintaining
1617    /// its own local ID for tracking dependencies.
1618    pub fn compile(&mut self, logic: &Value) -> Result<LogicId, String> {
1619        // Use global storage - compiles once and caches globally
1620        let _global_id = super::compiled_logic_store::compile_logic_value(logic)?;
1621
1622        // Get the compiled logic from global store (O(1) lookup)
1623        let compiled = super::compiled_logic_store::get_compiled_logic(_global_id)
1624            .ok_or_else(|| "Failed to retrieve compiled logic from global store".to_string())?;
1625
1626        // Track dependencies locally
1627        let deps = compiled.referenced_vars();
1628
1629        // Assign local ID for this instance
1630        let id = LogicId(self.next_id);
1631        self.next_id += 1;
1632
1633        // Store locally with instance-specific ID
1634        self.store.insert(id, compiled);
1635        self.dependencies.insert(id, deps);
1636
1637        Ok(id)
1638    }
1639
1640    /// Get a compiled logic by ID
1641    pub fn get(&self, id: &LogicId) -> Option<&CompiledLogic> {
1642        self.store.get(id)
1643    }
1644
1645    /// Remove a compiled logic by ID
1646    pub fn remove(&mut self, id: &LogicId) -> Option<CompiledLogic> {
1647        self.dependencies.remove(id);
1648        self.store.remove(id)
1649    }
1650
1651    /// Get dependencies for a logic ID
1652    pub fn get_dependencies(&self, id: &LogicId) -> Option<&[String]> {
1653        self.dependencies.get(id).map(|v| v.as_slice())
1654    }
1655}
1656
1657impl Default for CompiledLogicStore {
1658    fn default() -> Self {
1659        Self::new()
1660    }
1661}