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