Skip to main content

rust_rule_engine/rete/
network.rs

1#![allow(clippy::type_complexity)]
2
3use crate::rete::alpha::AlphaNode;
4use std::sync::Arc;
5/// Chuyển ConditionGroup sang ReteUlNode
6pub fn build_rete_ul_from_condition_group(
7    group: &crate::rete::auto_network::ConditionGroup,
8) -> ReteUlNode {
9    use crate::rete::auto_network::ConditionGroup;
10    match group {
11        ConditionGroup::Single(cond) => ReteUlNode::UlAlpha(AlphaNode {
12            field: cond.field.clone(),
13            operator: cond.operator.clone(),
14            value: cond.value.clone(),
15        }),
16        ConditionGroup::Compound {
17            left,
18            operator,
19            right,
20        } => match operator.as_str() {
21            "AND" => ReteUlNode::UlAnd(
22                Box::new(build_rete_ul_from_condition_group(left)),
23                Box::new(build_rete_ul_from_condition_group(right)),
24            ),
25            "OR" => ReteUlNode::UlOr(
26                Box::new(build_rete_ul_from_condition_group(left)),
27                Box::new(build_rete_ul_from_condition_group(right)),
28            ),
29            _ => ReteUlNode::UlAnd(
30                Box::new(build_rete_ul_from_condition_group(left)),
31                Box::new(build_rete_ul_from_condition_group(right)),
32            ),
33        },
34        ConditionGroup::Not(inner) => {
35            ReteUlNode::UlNot(Box::new(build_rete_ul_from_condition_group(inner)))
36        }
37        ConditionGroup::Exists(inner) => {
38            ReteUlNode::UlExists(Box::new(build_rete_ul_from_condition_group(inner)))
39        }
40        ConditionGroup::Forall(inner) => {
41            ReteUlNode::UlForall(Box::new(build_rete_ul_from_condition_group(inner)))
42        }
43    }
44}
45use std::collections::HashMap;
46
47/// Helper: Evaluate a condition string against facts (for accumulate)
48fn evaluate_condition_string(condition: &str, facts: &HashMap<String, String>) -> bool {
49    let condition = condition.trim();
50    let operators = ["==", "!=", ">=", "<=", ">", "<"];
51
52    for op in &operators {
53        if let Some(pos) = condition.find(op) {
54            let field = condition[..pos].trim();
55            let value_str = condition[pos + op.len()..]
56                .trim()
57                .trim_matches('"')
58                .trim_matches('\'');
59
60            if let Some(field_value) = facts.get(field) {
61                return compare_string_values(field_value, op, value_str);
62            } else {
63                return false;
64            }
65        }
66    }
67    false
68}
69
70/// Helper: Compare string values
71fn compare_string_values(field_value: &str, operator: &str, value_str: &str) -> bool {
72    // Try numeric comparison first
73    if let (Ok(field_num), Ok(val_num)) = (field_value.parse::<f64>(), value_str.parse::<f64>()) {
74        match operator {
75            "==" => (field_num - val_num).abs() < f64::EPSILON,
76            "!=" => (field_num - val_num).abs() >= f64::EPSILON,
77            ">" => field_num > val_num,
78            "<" => field_num < val_num,
79            ">=" => field_num >= val_num,
80            "<=" => field_num <= val_num,
81            _ => false,
82        }
83    } else {
84        // String comparison
85        match operator {
86            "==" => field_value == value_str,
87            "!=" => field_value != value_str,
88            _ => false,
89        }
90    }
91}
92
93/// Đánh giá mạng node RETE với facts
94pub fn evaluate_rete_ul_node(node: &ReteUlNode, facts: &HashMap<String, String>) -> bool {
95    match node {
96        ReteUlNode::UlAlpha(alpha) => {
97            let val = if alpha.field.contains('.') {
98                let parts: Vec<&str> = alpha.field.split('.').collect();
99                if parts.len() == 2 {
100                    let prefix = parts[0];
101                    let suffix = parts[1];
102                    facts
103                        .get(&format!("{}.{}", prefix, suffix))
104                        .or_else(|| facts.get(&format!("{}:{}", prefix, suffix)))
105                } else {
106                    facts.get(&alpha.field)
107                }
108            } else {
109                facts.get(&alpha.field)
110            };
111            if let Some(val) = val {
112                match alpha.operator.as_str() {
113                    "==" => val == &alpha.value,
114                    "!=" => val != &alpha.value,
115                    ">" => {
116                        val.parse::<f64>().unwrap_or(0.0)
117                            > alpha.value.parse::<f64>().unwrap_or(0.0)
118                    }
119                    "<" => {
120                        val.parse::<f64>().unwrap_or(0.0)
121                            < alpha.value.parse::<f64>().unwrap_or(0.0)
122                    }
123                    ">=" => {
124                        val.parse::<f64>().unwrap_or(0.0)
125                            >= alpha.value.parse::<f64>().unwrap_or(0.0)
126                    }
127                    "<=" => {
128                        val.parse::<f64>().unwrap_or(0.0)
129                            <= alpha.value.parse::<f64>().unwrap_or(0.0)
130                    }
131                    _ => false,
132                }
133            } else {
134                false
135            }
136        }
137        ReteUlNode::UlAnd(left, right) => {
138            evaluate_rete_ul_node(left, facts) && evaluate_rete_ul_node(right, facts)
139        }
140        ReteUlNode::UlOr(left, right) => {
141            evaluate_rete_ul_node(left, facts) || evaluate_rete_ul_node(right, facts)
142        }
143        ReteUlNode::UlNot(inner) => !evaluate_rete_ul_node(inner, facts),
144        ReteUlNode::UlExists(inner) => {
145            let target_field = match &**inner {
146                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
147                _ => "".to_string(),
148            };
149            if target_field.contains('.') {
150                let parts: Vec<&str> = target_field.split('.').collect();
151                if parts.len() == 2 {
152                    let prefix = parts[0];
153                    let suffix = parts[1];
154                    let filtered: Vec<_> = facts
155                        .iter()
156                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
157                        .collect();
158                    filtered.iter().any(|(_, value)| {
159                        let mut sub_facts = HashMap::new();
160                        sub_facts.insert(target_field.clone(), (*value).clone());
161                        evaluate_rete_ul_node(inner, &sub_facts)
162                    })
163                } else {
164                    facts.iter().any(|(field, value)| {
165                        let mut sub_facts = HashMap::new();
166                        sub_facts.insert(field.clone(), value.clone());
167                        evaluate_rete_ul_node(inner, &sub_facts)
168                    })
169                }
170            } else {
171                facts.iter().any(|(field, value)| {
172                    let mut sub_facts = HashMap::new();
173                    sub_facts.insert(field.clone(), value.clone());
174                    evaluate_rete_ul_node(inner, &sub_facts)
175                })
176            }
177        }
178        ReteUlNode::UlForall(inner) => {
179            let target_field = match &**inner {
180                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
181                _ => "".to_string(),
182            };
183            if target_field.contains('.') {
184                let parts: Vec<&str> = target_field.split('.').collect();
185                if parts.len() == 2 {
186                    let prefix = parts[0];
187                    let suffix = parts[1];
188                    let filtered: Vec<_> = facts
189                        .iter()
190                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
191                        .collect();
192                    if filtered.is_empty() {
193                        return true; // Vacuous truth: FORALL on empty set is TRUE
194                    }
195                    filtered.iter().all(|(_, value)| {
196                        let mut sub_facts = HashMap::new();
197                        sub_facts.insert(target_field.clone(), (*value).clone());
198                        evaluate_rete_ul_node(inner, &sub_facts)
199                    })
200                } else {
201                    facts.iter().all(|(field, value)| {
202                        let mut sub_facts = HashMap::new();
203                        sub_facts.insert(field.clone(), value.clone());
204                        evaluate_rete_ul_node(inner, &sub_facts)
205                    })
206                }
207            } else {
208                facts.iter().all(|(field, value)| {
209                    let mut sub_facts = HashMap::new();
210                    sub_facts.insert(field.clone(), value.clone());
211                    evaluate_rete_ul_node(inner, &sub_facts)
212                })
213            }
214        }
215        ReteUlNode::UlAccumulate {
216            source_pattern,
217            extract_field,
218            source_conditions,
219            function,
220            ..
221        } => {
222            // Evaluate accumulate: collect matching facts and run function
223
224            let pattern_prefix = format!("{}.", source_pattern);
225            let mut matching_values = Vec::new();
226
227            // Group facts by instance
228            let mut instances: std::collections::HashMap<
229                String,
230                std::collections::HashMap<String, String>,
231            > = std::collections::HashMap::new();
232
233            for (key, value) in facts {
234                if key.starts_with(&pattern_prefix) {
235                    let parts: Vec<&str> = key
236                        .strip_prefix(&pattern_prefix)
237                        .unwrap()
238                        .split('.')
239                        .collect();
240
241                    if parts.len() >= 2 {
242                        let instance_id = parts[0];
243                        let field_name = parts[1..].join(".");
244
245                        instances
246                            .entry(instance_id.to_string())
247                            .or_default()
248                            .insert(field_name, value.clone());
249                    } else if parts.len() == 1 {
250                        instances
251                            .entry("default".to_string())
252                            .or_default()
253                            .insert(parts[0].to_string(), value.clone());
254                    }
255                }
256            }
257
258            // Filter instances by source conditions
259            for (_instance_id, instance_facts) in instances {
260                let mut matches = true;
261
262                for condition_str in source_conditions {
263                    if !evaluate_condition_string(condition_str, &instance_facts) {
264                        matches = false;
265                        break;
266                    }
267                }
268
269                if matches {
270                    if let Some(value_str) = instance_facts.get(extract_field) {
271                        // Convert string to FactValue
272                        let fact_value = if let Ok(i) = value_str.parse::<i64>() {
273                            super::facts::FactValue::Integer(i)
274                        } else if let Ok(f) = value_str.parse::<f64>() {
275                            super::facts::FactValue::Float(f)
276                        } else if let Ok(b) = value_str.parse::<bool>() {
277                            super::facts::FactValue::Boolean(b)
278                        } else {
279                            super::facts::FactValue::String(value_str.clone())
280                        };
281                        matching_values.push(fact_value);
282                    }
283                }
284            }
285
286            // Run accumulate function - result determines if condition passes
287            let has_results = !matching_values.is_empty();
288
289            match function.as_str() {
290                "count" => has_results, // Count passes if there are any matches
291                "sum" | "average" | "min" | "max" => {
292                    // These functions need at least one value
293                    has_results
294                }
295                _ => true, // Unknown function - allow to continue
296            }
297        }
298        ReteUlNode::UlMultiField {
299            field,
300            operation,
301            value,
302            operator,
303            compare_value,
304        } => {
305            // Evaluate multi-field operations
306            // Note: For HashMap<String, String> facts, we need to parse array representations
307            // This is a simplified implementation
308            let field_value = facts.get(field);
309
310            match operation.as_str() {
311                "empty" => {
312                    // Check if field is empty or doesn't exist
313                    field_value
314                        .map(|v| v.is_empty() || v == "[]")
315                        .unwrap_or(true)
316                }
317                "not_empty" => {
318                    // Check if field is not empty
319                    field_value
320                        .map(|v| !v.is_empty() && v != "[]")
321                        .unwrap_or(false)
322                }
323                "count" => {
324                    if let Some(val) = field_value {
325                        // Try to parse as array and count elements
326                        // Simple heuristic: count commas + 1 if not empty
327                        let count = if val.starts_with('[') && val.ends_with(']') {
328                            let inner = &val[1..val.len() - 1];
329                            if inner.trim().is_empty() {
330                                0
331                            } else {
332                                inner.split(',').count()
333                            }
334                        } else {
335                            0
336                        };
337
338                        // Compare count with compare_value if operator exists
339                        if let (Some(op), Some(cmp_val)) = (operator, compare_value) {
340                            let cmp_num = cmp_val.parse::<i64>().unwrap_or(0);
341                            match op.as_str() {
342                                ">" => (count as i64) > cmp_num,
343                                "<" => (count as i64) < cmp_num,
344                                ">=" => (count as i64) >= cmp_num,
345                                "<=" => (count as i64) <= cmp_num,
346                                "==" => (count as i64) == cmp_num,
347                                "!=" => (count as i64) != cmp_num,
348                                _ => false,
349                            }
350                        } else {
351                            count > 0
352                        }
353                    } else {
354                        false
355                    }
356                }
357                "contains" => {
358                    if let (Some(val), Some(search)) = (field_value, value) {
359                        // Simple contains check for string representation
360                        val.contains(search)
361                    } else {
362                        false
363                    }
364                }
365                _ => {
366                    // Unknown operation
367                    false
368                }
369            }
370        }
371        #[cfg(feature = "streaming")]
372        ReteUlNode::UlStream { .. } => {
373            // Stream nodes are handled by streaming engine
374            // For HashMap evaluation context, return true
375            true
376        }
377        ReteUlNode::UlFunctionCall { .. } => false, // needs custom_fns, not available here
378        ReteUlNode::UlTerminal(_) => true,
379    }
380}
381
382/// RETE-UL: Unified Logic Node
383#[derive(Debug, Clone)]
384pub enum ReteUlNode {
385    UlAlpha(AlphaNode),
386    UlAnd(Box<ReteUlNode>, Box<ReteUlNode>),
387    UlOr(Box<ReteUlNode>, Box<ReteUlNode>),
388    UlNot(Box<ReteUlNode>),
389    UlExists(Box<ReteUlNode>),
390    UlForall(Box<ReteUlNode>),
391    UlAccumulate {
392        result_var: String,
393        source_pattern: String,
394        extract_field: String,
395        source_conditions: Vec<String>,
396        function: String,
397        function_arg: String,
398    },
399    UlMultiField {
400        field: String,
401        operation: String, // "collect", "contains", "count", "first", "last", "empty", "not_empty"
402        value: Option<String>, // For operations like "contains"
403        operator: Option<String>, // For operations like "count > 5"
404        compare_value: Option<String>, // For operations like "count > 5"
405    },
406    #[cfg(feature = "streaming")]
407    UlStream {
408        var_name: String,
409        event_type: Option<String>,
410        stream_name: String,
411        window: Option<StreamWindowSpec>,
412    },
413    /// Custom function call in WHEN: `funcName(arg1, arg2, ...) == true`
414    /// `args` are fact field paths (e.g. "Fact.text") or string literals.
415    UlFunctionCall {
416        name:     String,
417        args:     Vec<String>,
418        operator: String,
419        value:    String,
420    },
421    UlTerminal(String), // Rule name
422}
423
424#[cfg(feature = "streaming")]
425#[derive(Debug, Clone, PartialEq)]
426pub struct StreamWindowSpec {
427    pub duration: std::time::Duration,
428    pub window_type: StreamWindowTypeRete,
429}
430
431#[cfg(feature = "streaming")]
432#[derive(Debug, Clone, PartialEq)]
433pub enum StreamWindowTypeRete {
434    Sliding,
435    Tumbling,
436    Session { timeout: std::time::Duration },
437}
438
439impl ReteUlNode {
440    /// Evaluate with typed facts (no custom functions — for backward compat).
441    pub fn evaluate_typed(&self, facts: &super::facts::TypedFacts) -> bool {
442        evaluate_rete_ul_node_typed(self, facts, &std::collections::HashMap::new())
443    }
444}
445
446/// RETE-UL Rule Struct
447pub struct ReteUlRule {
448    pub name: String,
449    pub node: ReteUlNode,
450    pub priority: i32,
451    pub no_loop: bool,
452    pub action: Arc<dyn Fn(&mut std::collections::HashMap<String, String>) + Send + Sync>,
453}
454
455/// Drools-style RETE-UL rule firing loop
456/// Fires all matching rules, updates facts, repeats until no more rules can fire
457pub fn fire_rete_ul_rules(
458    rules: &mut [(
459        String,
460        ReteUlNode,
461        Box<dyn FnMut(&mut std::collections::HashMap<String, String>)>,
462    )],
463    facts: &mut std::collections::HashMap<String, String>,
464) -> Vec<String> {
465    let mut fired_rules = Vec::new();
466    let mut changed = true;
467    while changed {
468        changed = false;
469        for (rule_name, node, action) in rules.iter_mut() {
470            let fired_flag = format!("{}_fired", rule_name);
471            if facts.get(&fired_flag) == Some(&"true".to_string()) {
472                continue;
473            }
474            if evaluate_rete_ul_node(node, facts) {
475                action(facts);
476                facts.insert(fired_flag.clone(), "true".to_string());
477                fired_rules.push(rule_name.clone());
478                changed = true;
479            }
480        }
481    }
482    fired_rules
483}
484
485/// Drools-style RETE-UL rule firing loop with agenda and control
486pub fn fire_rete_ul_rules_with_agenda(
487    rules: &mut [ReteUlRule],
488    facts: &mut std::collections::HashMap<String, String>,
489) -> Vec<String> {
490    let mut fired_rules = Vec::new();
491    let mut fired_flags = std::collections::HashSet::new();
492    let max_iterations = 100; // Prevent infinite loops
493    let mut iterations = 0;
494
495    loop {
496        iterations += 1;
497        if iterations > max_iterations {
498            eprintln!(
499                "Warning: RETE engine reached max iterations ({})",
500                max_iterations
501            );
502            break;
503        }
504
505        // Build agenda: rules that match and haven't been fired yet
506        let mut agenda: Vec<usize> = rules
507            .iter()
508            .enumerate()
509            .filter(|(_, rule)| {
510                // Check if rule already fired
511                if fired_flags.contains(&rule.name) {
512                    return false;
513                }
514                // Check if rule matches current facts
515                evaluate_rete_ul_node(&rule.node, facts)
516            })
517            .map(|(i, _)| i)
518            .collect();
519
520        // If no rules to fire, we're done
521        if agenda.is_empty() {
522            break;
523        }
524
525        // Sort agenda by priority (descending)
526        agenda.sort_by_key(|&i| -rules[i].priority);
527
528        // Fire all rules in agenda
529        for &i in &agenda {
530            let rule = &mut rules[i];
531
532            // Execute rule action
533            (rule.action)(facts);
534
535            // Mark as fired
536            fired_rules.push(rule.name.clone());
537            fired_flags.insert(rule.name.clone());
538
539            let fired_flag = format!("{}_fired", rule.name);
540            facts.insert(fired_flag, "true".to_string());
541        }
542
543        // If no_loop is enabled for all rules, stop after one iteration
544        if rules.iter().all(|r| r.no_loop) {
545            break;
546        }
547    }
548
549    fired_rules
550}
551
552/// RETE-UL Engine with cached nodes (Performance optimized!)
553/// This engine builds RETE nodes once and reuses them, avoiding expensive rebuilds
554pub struct ReteUlEngine {
555    rules: Vec<ReteUlRule>,
556    facts: std::collections::HashMap<String, String>,
557}
558
559impl Default for ReteUlEngine {
560    fn default() -> Self {
561        Self::new()
562    }
563}
564
565impl ReteUlEngine {
566    /// Create new engine from Rule definitions (nodes are built and cached once)
567    pub fn new() -> Self {
568        Self {
569            rules: Vec::new(),
570            facts: std::collections::HashMap::new(),
571        }
572    }
573
574    /// Add a rule with custom action closure
575    pub fn add_rule_with_action<F>(
576        &mut self,
577        name: String,
578        node: ReteUlNode,
579        priority: i32,
580        no_loop: bool,
581        action: F,
582    ) where
583        F: Fn(&mut std::collections::HashMap<String, String>) + Send + Sync + 'static,
584    {
585        self.rules.push(ReteUlRule {
586            name,
587            node,
588            priority,
589            no_loop,
590            action: Arc::new(action),
591        });
592    }
593
594    /// Add a rule from Rule definition (auto-build node once and cache)
595    pub fn add_rule_from_definition(
596        &mut self,
597        rule: &crate::rete::auto_network::Rule,
598        priority: i32,
599        no_loop: bool,
600    ) {
601        let node = build_rete_ul_from_condition_group(&rule.conditions);
602        let rule_name = rule.name.clone();
603
604        // Default action: just mark as fired
605        let action = Arc::new(
606            move |facts: &mut std::collections::HashMap<String, String>| {
607                facts.insert(format!("{}_executed", rule_name), "true".to_string());
608            },
609        );
610
611        self.rules.push(ReteUlRule {
612            name: rule.name.clone(),
613            node,
614            priority,
615            no_loop,
616            action,
617        });
618    }
619
620    /// Set a fact
621    pub fn set_fact(&mut self, key: String, value: String) {
622        self.facts.insert(key, value);
623    }
624
625    /// Get a fact
626    pub fn get_fact(&self, key: &str) -> Option<&String> {
627        self.facts.get(key)
628    }
629
630    /// Remove a fact
631    pub fn remove_fact(&mut self, key: &str) -> Option<String> {
632        self.facts.remove(key)
633    }
634
635    /// Get all facts
636    pub fn get_all_facts(&self) -> &std::collections::HashMap<String, String> {
637        &self.facts
638    }
639
640    /// Clear all facts
641    pub fn clear_facts(&mut self) {
642        self.facts.clear();
643    }
644
645    /// Fire all rules with agenda (using cached nodes - NO rebuild!)
646    pub fn fire_all(&mut self) -> Vec<String> {
647        fire_rete_ul_rules_with_agenda(&mut self.rules, &mut self.facts)
648    }
649
650    /// Check if a specific rule matches current facts (without firing)
651    pub fn matches(&self, rule_name: &str) -> bool {
652        self.rules
653            .iter()
654            .find(|r| r.name == rule_name)
655            .map(|r| evaluate_rete_ul_node(&r.node, &self.facts))
656            .unwrap_or(false)
657    }
658
659    /// Get all matching rules (without firing)
660    pub fn get_matching_rules(&self) -> Vec<&str> {
661        self.rules
662            .iter()
663            .filter(|r| evaluate_rete_ul_node(&r.node, &self.facts))
664            .map(|r| r.name.as_str())
665            .collect()
666    }
667
668    /// Reset fired flags (allow rules to fire again)
669    pub fn reset_fired_flags(&mut self) {
670        let keys_to_remove: Vec<_> = self
671            .facts
672            .keys()
673            .filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
674            .cloned()
675            .collect();
676        for key in keys_to_remove {
677            self.facts.remove(&key);
678        }
679    }
680}
681
682// ============================================================================
683// Typed Facts Support (NEW!)
684// ============================================================================
685
686use super::facts::{FactValue, TypedFacts};
687
688/// Evaluate RETE-UL node with typed facts.
689/// `custom_fns` is the engine's registered function map — needed for `UlFunctionCall` nodes.
690pub fn evaluate_rete_ul_node_typed(
691    node: &ReteUlNode,
692    facts: &TypedFacts,
693    custom_fns: &std::collections::HashMap<String, super::propagation::ReteCustomFunction>,
694) -> bool {
695    match node {
696        ReteUlNode::UlAlpha(alpha) => alpha.matches_typed(facts),
697        ReteUlNode::UlAnd(left, right) => {
698            evaluate_rete_ul_node_typed(left, facts, custom_fns)
699                && evaluate_rete_ul_node_typed(right, facts, custom_fns)
700        }
701        ReteUlNode::UlOr(left, right) => {
702            evaluate_rete_ul_node_typed(left, facts, custom_fns)
703                || evaluate_rete_ul_node_typed(right, facts, custom_fns)
704        }
705        ReteUlNode::UlNot(inner) => !evaluate_rete_ul_node_typed(inner, facts, custom_fns),
706        ReteUlNode::UlExists(inner) => {
707            let target_field = match &**inner {
708                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
709                _ => "".to_string(),
710            };
711            if target_field.contains('.') {
712                let parts: Vec<&str> = target_field.split('.').collect();
713                if parts.len() == 2 {
714                    let prefix = parts[0];
715                    let suffix = parts[1];
716                    let filtered: Vec<_> = facts
717                        .get_all()
718                        .iter()
719                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
720                        .collect();
721                    filtered
722                        .iter()
723                        .any(|(_, _)| evaluate_rete_ul_node_typed(inner, facts, custom_fns))
724                } else {
725                    evaluate_rete_ul_node_typed(inner, facts, custom_fns)
726                }
727            } else {
728                evaluate_rete_ul_node_typed(inner, facts, custom_fns)
729            }
730        }
731        ReteUlNode::UlForall(inner) => {
732            let target_field = match &**inner {
733                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
734                _ => "".to_string(),
735            };
736            if target_field.contains('.') {
737                let parts: Vec<&str> = target_field.split('.').collect();
738                if parts.len() == 2 {
739                    let prefix = parts[0];
740                    let suffix = parts[1];
741                    let filtered: Vec<_> = facts
742                        .get_all()
743                        .iter()
744                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
745                        .collect();
746                    if filtered.is_empty() {
747                        return true; // Vacuous truth
748                    }
749                    filtered
750                        .iter()
751                        .all(|(_, _)| evaluate_rete_ul_node_typed(inner, facts, custom_fns))
752                } else {
753                    if facts.get_all().is_empty() {
754                        return true; // Vacuous truth
755                    }
756                    evaluate_rete_ul_node_typed(inner, facts, custom_fns)
757                }
758            } else {
759                if facts.get_all().is_empty() {
760                    return true; // Vacuous truth
761                }
762                evaluate_rete_ul_node_typed(inner, facts, custom_fns)
763            }
764        }
765        ReteUlNode::UlAccumulate {
766            source_pattern,
767            extract_field,
768            source_conditions,
769            function,
770            ..
771        } => {
772            // Evaluate accumulate with typed facts
773
774            let pattern_prefix = format!("{}.", source_pattern);
775            let mut matching_values = Vec::new();
776
777            // Group facts by instance
778            let mut instances: std::collections::HashMap<
779                String,
780                std::collections::HashMap<String, FactValue>,
781            > = std::collections::HashMap::new();
782
783            for (key, value) in facts.get_all() {
784                if key.starts_with(&pattern_prefix) {
785                    let parts: Vec<&str> = key
786                        .strip_prefix(&pattern_prefix)
787                        .unwrap()
788                        .split('.')
789                        .collect();
790
791                    if parts.len() >= 2 {
792                        let instance_id = parts[0];
793                        let field_name = parts[1..].join(".");
794
795                        instances
796                            .entry(instance_id.to_string())
797                            .or_default()
798                            .insert(field_name, value.clone());
799                    } else if parts.len() == 1 {
800                        instances
801                            .entry("default".to_string())
802                            .or_default()
803                            .insert(parts[0].to_string(), value.clone());
804                    }
805                }
806            }
807
808            // Filter instances by source conditions
809            for (_instance_id, instance_facts) in instances {
810                let mut matches = true;
811
812                for condition_str in source_conditions {
813                    // Convert FactValues to strings for condition evaluation
814                    let string_facts: HashMap<String, String> = instance_facts
815                        .iter()
816                        .map(|(k, v)| (k.clone(), format!("{:?}", v)))
817                        .collect();
818
819                    if !evaluate_condition_string(condition_str, &string_facts) {
820                        matches = false;
821                        break;
822                    }
823                }
824
825                if matches {
826                    if let Some(value) = instance_facts.get(extract_field) {
827                        matching_values.push(value.clone());
828                    }
829                }
830            }
831
832            // Run accumulate function - result determines if condition passes
833            let has_results = !matching_values.is_empty();
834
835            match function.as_str() {
836                "count" => has_results,
837                "sum" | "average" | "min" | "max" => has_results,
838                _ => true,
839            }
840        }
841        ReteUlNode::UlMultiField {
842            field,
843            operation,
844            value,
845            operator,
846            compare_value,
847        } => {
848            // Evaluate multi-field operations on TypedFacts
849            use super::facts::FactValue;
850
851            let field_value = facts.get(field);
852
853            match operation.as_str() {
854                "empty" => {
855                    // Check if field is an empty array
856                    if let Some(FactValue::Array(arr)) = field_value {
857                        arr.is_empty()
858                    } else {
859                        // Field doesn't exist or is not an array, consider it empty
860                        true
861                    }
862                }
863                "not_empty" => {
864                    // Check if field is a non-empty array
865                    if let Some(FactValue::Array(arr)) = field_value {
866                        !arr.is_empty()
867                    } else {
868                        false
869                    }
870                }
871                "count" => {
872                    if let Some(FactValue::Array(arr)) = field_value {
873                        let count = arr.len() as i64;
874
875                        // Compare count with compare_value if operator exists
876                        if let (Some(op), Some(cmp_val)) = (operator, compare_value) {
877                            let cmp_num = cmp_val.parse::<i64>().unwrap_or(0);
878                            match op.as_str() {
879                                ">" => count > cmp_num,
880                                "<" => count < cmp_num,
881                                ">=" => count >= cmp_num,
882                                "<=" => count <= cmp_num,
883                                "==" => count == cmp_num,
884                                "!=" => count != cmp_num,
885                                _ => false,
886                            }
887                        } else {
888                            count > 0
889                        }
890                    } else {
891                        false
892                    }
893                }
894                "contains" => {
895                    if let (Some(FactValue::Array(arr)), Some(search)) = (field_value, value) {
896                        // Parse search value and check if array contains it
897                        // For simplicity, check as string
898                        arr.iter().any(|item| match item {
899                            FactValue::String(s) => s == search,
900                            FactValue::Integer(i) => i.to_string() == *search,
901                            FactValue::Float(f) => f.to_string() == *search,
902                            FactValue::Boolean(b) => b.to_string() == *search,
903                            _ => false,
904                        })
905                    } else {
906                        false
907                    }
908                }
909                "first" => {
910                    // Get first element - for pattern matching, just check it exists
911                    if let Some(FactValue::Array(arr)) = field_value {
912                        !arr.is_empty()
913                    } else {
914                        false
915                    }
916                }
917                "last" => {
918                    // Get last element - for pattern matching, just check it exists
919                    if let Some(FactValue::Array(arr)) = field_value {
920                        !arr.is_empty()
921                    } else {
922                        false
923                    }
924                }
925                "collect" => {
926                    // Collect operation - for pattern matching, just check field is an array
927                    matches!(field_value, Some(FactValue::Array(_)))
928                }
929                _ => {
930                    // Unknown operation
931                    false
932                }
933            }
934        }
935        ReteUlNode::UlFunctionCall { name, args, operator, value } => {
936            // Resolve each arg: fact field reference first, then string/numeric literal.
937            // Quoted args like `"pattern"` arrive with surrounding quotes from the parser.
938            let resolved: Vec<FactValue> = args.iter().map(|arg| {
939                if let Some(v) = facts.get(arg) {
940                    return v.clone();
941                }
942                // Strip surrounding quotes for string literals
943                if (arg.starts_with('"') && arg.ends_with('"'))
944                    || (arg.starts_with('\'') && arg.ends_with('\''))
945                {
946                    return FactValue::String(arg[1..arg.len() - 1].to_string());
947                }
948                // Numeric literal
949                if let Ok(n) = arg.parse::<f64>() {
950                    return FactValue::from(n);
951                }
952                FactValue::String(arg.clone())
953            }).collect();
954
955            let result = if let Some(func) = custom_fns.get(name) {
956                func(&resolved, facts).unwrap_or(FactValue::Boolean(false))
957            } else {
958                FactValue::Boolean(false)
959            };
960
961            let expected = match value.as_str() {
962                "true"  => FactValue::Boolean(true),
963                "false" => FactValue::Boolean(false),
964                s       => if let Ok(n) = s.parse::<f64>() {
965                    FactValue::from(n)
966                } else {
967                    FactValue::String(s.to_string())
968                },
969            };
970
971            result.compare(operator, &expected)
972        }
973        #[cfg(feature = "streaming")]
974        ReteUlNode::UlStream { .. } => {
975            // Stream nodes are handled by streaming engine
976            // For TypedFacts evaluation context, return true
977            true
978        }
979        ReteUlNode::UlTerminal(_) => true,
980    }
981}
982
983/// Typed RETE-UL Rule
984pub struct TypedReteUlRule {
985    pub name: String,
986    pub node: ReteUlNode,
987    pub priority: i32,
988    pub no_loop: bool,
989    pub action: Arc<dyn Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync>,
990}
991
992/// Typed RETE-UL Engine with cached nodes (Performance + Type Safety!)
993/// This is the recommended engine for new code
994pub struct TypedReteUlEngine {
995    rules: Vec<TypedReteUlRule>,
996    facts: TypedFacts,
997}
998
999impl TypedReteUlEngine {
1000    /// Create new typed engine
1001    pub fn new() -> Self {
1002        Self {
1003            rules: Vec::new(),
1004            facts: TypedFacts::new(),
1005        }
1006    }
1007
1008    /// Add a rule with custom action
1009    pub fn add_rule_with_action<F>(
1010        &mut self,
1011        name: String,
1012        node: ReteUlNode,
1013        priority: i32,
1014        no_loop: bool,
1015        action: F,
1016    ) where
1017        F: Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync + 'static,
1018    {
1019        self.rules.push(TypedReteUlRule {
1020            name,
1021            node,
1022            priority,
1023            no_loop,
1024            action: Arc::new(action),
1025        });
1026    }
1027
1028    /// Add a rule from Rule definition
1029    pub fn add_rule_from_definition(
1030        &mut self,
1031        rule: &crate::rete::auto_network::Rule,
1032        priority: i32,
1033        no_loop: bool,
1034    ) {
1035        let node = build_rete_ul_from_condition_group(&rule.conditions);
1036        let rule_name = rule.name.clone();
1037
1038        let action = Arc::new(
1039            move |facts: &mut TypedFacts, _results: &mut super::ActionResults| {
1040                facts.set(format!("{}_executed", rule_name), true);
1041            },
1042        );
1043
1044        self.rules.push(TypedReteUlRule {
1045            name: rule.name.clone(),
1046            node,
1047            priority,
1048            no_loop,
1049            action,
1050        });
1051    }
1052
1053    /// Set a fact with typed value
1054    pub fn set_fact<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
1055        self.facts.set(key, value);
1056    }
1057
1058    /// Get a fact
1059    pub fn get_fact(&self, key: &str) -> Option<&FactValue> {
1060        self.facts.get(key)
1061    }
1062
1063    /// Remove a fact
1064    pub fn remove_fact(&mut self, key: &str) -> Option<FactValue> {
1065        self.facts.remove(key)
1066    }
1067
1068    /// Get all facts
1069    pub fn get_all_facts(&self) -> &TypedFacts {
1070        &self.facts
1071    }
1072
1073    /// Clear all facts
1074    pub fn clear_facts(&mut self) {
1075        self.facts.clear();
1076    }
1077
1078    /// Fire all rules with agenda (using cached nodes + typed evaluation!)
1079    pub fn fire_all(&mut self) -> Vec<String> {
1080        let mut fired_rules = Vec::new();
1081        let mut agenda: Vec<usize>;
1082        let mut changed = true;
1083        let mut fired_flags = std::collections::HashSet::new();
1084
1085        while changed {
1086            changed = false;
1087
1088            // Build agenda: rules that match and not fired
1089            agenda = self
1090                .rules
1091                .iter()
1092                .enumerate()
1093                .filter(|(_, rule)| {
1094                    let fired_flag = format!("{}_fired", rule.name);
1095                    let already_fired = fired_flags.contains(&rule.name)
1096                        || self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
1097                    !rule.no_loop || !already_fired
1098                })
1099                .filter(|(_, rule)| evaluate_rete_ul_node_typed(&rule.node, &self.facts, &std::collections::HashMap::new()))
1100                .map(|(i, _)| i)
1101                .collect();
1102
1103            // Sort by priority (descending)
1104            agenda.sort_by_key(|&i| -self.rules[i].priority);
1105
1106            for &i in &agenda {
1107                let rule = &mut self.rules[i];
1108                let fired_flag = format!("{}_fired", rule.name);
1109                let already_fired = fired_flags.contains(&rule.name)
1110                    || self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
1111
1112                if rule.no_loop && already_fired {
1113                    continue;
1114                }
1115
1116                let mut action_results = super::ActionResults::new();
1117                (rule.action)(&mut self.facts, &mut action_results);
1118                // Note: ActionResults not processed in TypedReteUlEngine (legacy engine)
1119                // Use IncrementalEngine for full ActionResults support
1120
1121                fired_rules.push(rule.name.clone());
1122                fired_flags.insert(rule.name.clone());
1123                self.facts.set(fired_flag, true);
1124                changed = true;
1125            }
1126        }
1127
1128        fired_rules
1129    }
1130
1131    /// Check if a specific rule matches current facts
1132    pub fn matches(&self, rule_name: &str) -> bool {
1133        self.rules
1134            .iter()
1135            .find(|r| r.name == rule_name)
1136            .map(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts, &std::collections::HashMap::new()))
1137            .unwrap_or(false)
1138    }
1139
1140    /// Get all matching rules
1141    pub fn get_matching_rules(&self) -> Vec<&str> {
1142        self.rules
1143            .iter()
1144            .filter(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts, &std::collections::HashMap::new()))
1145            .map(|r| r.name.as_str())
1146            .collect()
1147    }
1148
1149    /// Reset fired flags
1150    pub fn reset_fired_flags(&mut self) {
1151        let keys_to_remove: Vec<_> = self
1152            .facts
1153            .get_all()
1154            .keys()
1155            .filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
1156            .cloned()
1157            .collect();
1158        for key in keys_to_remove {
1159            self.facts.remove(&key);
1160        }
1161    }
1162}
1163
1164impl Default for TypedReteUlEngine {
1165    fn default() -> Self {
1166        Self::new()
1167    }
1168}