rust_rule_engine/rete/
network.rs

1use crate::rete::alpha::AlphaNode;
2/// Chuyển ConditionGroup sang ReteUlNode
3pub fn build_rete_ul_from_condition_group(group: &crate::rete::auto_network::ConditionGroup) -> ReteUlNode {
4    use crate::rete::auto_network::ConditionGroup;
5    match group {
6        ConditionGroup::Single(cond) => {
7            ReteUlNode::UlAlpha(AlphaNode {
8                field: cond.field.clone(),
9                operator: cond.operator.clone(),
10                value: cond.value.clone(),
11            })
12        }
13        ConditionGroup::Compound { left, operator, right } => {
14            match operator.as_str() {
15                "AND" => ReteUlNode::UlAnd(
16                    Box::new(build_rete_ul_from_condition_group(left)),
17                    Box::new(build_rete_ul_from_condition_group(right)),
18                ),
19                "OR" => ReteUlNode::UlOr(
20                    Box::new(build_rete_ul_from_condition_group(left)),
21                    Box::new(build_rete_ul_from_condition_group(right)),
22                ),
23                _ => ReteUlNode::UlAnd(
24                    Box::new(build_rete_ul_from_condition_group(left)),
25                    Box::new(build_rete_ul_from_condition_group(right)),
26                ),
27            }
28        }
29        ConditionGroup::Not(inner) => {
30            ReteUlNode::UlNot(Box::new(build_rete_ul_from_condition_group(inner)))
31        }
32        ConditionGroup::Exists(inner) => {
33            ReteUlNode::UlExists(Box::new(build_rete_ul_from_condition_group(inner)))
34        }
35        ConditionGroup::Forall(inner) => {
36            ReteUlNode::UlForall(Box::new(build_rete_ul_from_condition_group(inner)))
37        }
38    }
39}
40use std::collections::HashMap;
41
42/// Helper: Evaluate a condition string against facts (for accumulate)
43fn evaluate_condition_string(condition: &str, facts: &HashMap<String, String>) -> bool {
44    let condition = condition.trim();
45    let operators = ["==", "!=", ">=", "<=", ">", "<"];
46
47    for op in &operators {
48        if let Some(pos) = condition.find(op) {
49            let field = condition[..pos].trim();
50            let value_str = condition[pos + op.len()..]
51                .trim()
52                .trim_matches('"')
53                .trim_matches('\'');
54
55            if let Some(field_value) = facts.get(field) {
56                return compare_string_values(field_value, op, value_str);
57            } else {
58                return false;
59            }
60        }
61    }
62    false
63}
64
65/// Helper: Compare string values
66fn compare_string_values(field_value: &str, operator: &str, value_str: &str) -> bool {
67    // Try numeric comparison first
68    if let (Ok(field_num), Ok(val_num)) = (field_value.parse::<f64>(), value_str.parse::<f64>()) {
69        match operator {
70            "==" => (field_num - val_num).abs() < f64::EPSILON,
71            "!=" => (field_num - val_num).abs() >= f64::EPSILON,
72            ">" => field_num > val_num,
73            "<" => field_num < val_num,
74            ">=" => field_num >= val_num,
75            "<=" => field_num <= val_num,
76            _ => false,
77        }
78    } else {
79        // String comparison
80        match operator {
81            "==" => field_value == value_str,
82            "!=" => field_value != value_str,
83            _ => false,
84        }
85    }
86}
87
88/// Đánh giá mạng node RETE với facts
89pub fn evaluate_rete_ul_node(node: &ReteUlNode, facts: &HashMap<String, String>) -> bool {
90    match node {
91        ReteUlNode::UlAlpha(alpha) => {
92            let val = if alpha.field.contains('.') {
93                let parts: Vec<&str> = alpha.field.split('.').collect();
94                if parts.len() == 2 {
95                    let prefix = parts[0];
96                    let suffix = parts[1];
97                    facts.get(&format!("{}.{}", prefix, suffix)).or_else(|| facts.get(&format!("{}:{}", prefix, suffix)))
98                } else {
99                    facts.get(&alpha.field)
100                }
101            } else {
102                facts.get(&alpha.field)
103            };
104            if let Some(val) = val {
105                match alpha.operator.as_str() {
106                    "==" => val == &alpha.value,
107                    "!=" => val != &alpha.value,
108                    ">" => val.parse::<f64>().unwrap_or(0.0) > alpha.value.parse::<f64>().unwrap_or(0.0),
109                    "<" => val.parse::<f64>().unwrap_or(0.0) < alpha.value.parse::<f64>().unwrap_or(0.0),
110                    ">=" => val.parse::<f64>().unwrap_or(0.0) >= alpha.value.parse::<f64>().unwrap_or(0.0),
111                    "<=" => val.parse::<f64>().unwrap_or(0.0) <= alpha.value.parse::<f64>().unwrap_or(0.0),
112                    _ => false,
113                }
114            } else {
115                false
116            }
117        }
118        ReteUlNode::UlAnd(left, right) => {
119            evaluate_rete_ul_node(left, facts) && evaluate_rete_ul_node(right, facts)
120        }
121        ReteUlNode::UlOr(left, right) => {
122            evaluate_rete_ul_node(left, facts) || evaluate_rete_ul_node(right, facts)
123        }
124        ReteUlNode::UlNot(inner) => {
125            !evaluate_rete_ul_node(inner, facts)
126        }
127        ReteUlNode::UlExists(inner) => {
128            let target_field = match &**inner {
129                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
130                _ => "".to_string(),
131            };
132            if target_field.contains('.') {
133                let parts: Vec<&str> = target_field.split('.').collect();
134                if parts.len() == 2 {
135                    let prefix = parts[0];
136                    let suffix = parts[1];
137                    let filtered: Vec<_> = facts.iter()
138                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
139                        .collect();
140                    filtered.iter().any(|(_, value)| {
141                        let mut sub_facts = HashMap::new();
142                        sub_facts.insert(target_field.clone(), (*value).clone());
143                        evaluate_rete_ul_node(inner, &sub_facts)
144                    })
145                } else {
146                    facts.iter().any(|(field, value)| {
147                        let mut sub_facts = HashMap::new();
148                        sub_facts.insert(field.clone(), value.clone());
149                        evaluate_rete_ul_node(inner, &sub_facts)
150                    })
151                }
152            } else {
153                facts.iter().any(|(field, value)| {
154                    let mut sub_facts = HashMap::new();
155                    sub_facts.insert(field.clone(), value.clone());
156                    evaluate_rete_ul_node(inner, &sub_facts)
157                })
158            }
159        }
160        ReteUlNode::UlForall(inner) => {
161            let target_field = match &**inner {
162                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
163                _ => "".to_string(),
164            };
165            if target_field.contains('.') {
166                let parts: Vec<&str> = target_field.split('.').collect();
167                if parts.len() == 2 {
168                    let prefix = parts[0];
169                    let suffix = parts[1];
170                    let filtered: Vec<_> = facts.iter()
171                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
172                        .collect();
173                    if filtered.is_empty() {
174                        return true; // Vacuous truth: FORALL on empty set is TRUE
175                    }
176                    filtered.iter().all(|(_, value)| {
177                        let mut sub_facts = HashMap::new();
178                        sub_facts.insert(target_field.clone(), (*value).clone());
179                        evaluate_rete_ul_node(inner, &sub_facts)
180                    })
181                } else {
182                    facts.iter().all(|(field, value)| {
183                        let mut sub_facts = HashMap::new();
184                        sub_facts.insert(field.clone(), value.clone());
185                        evaluate_rete_ul_node(inner, &sub_facts)
186                    })
187                }
188            } else {
189                facts.iter().all(|(field, value)| {
190                    let mut sub_facts = HashMap::new();
191                    sub_facts.insert(field.clone(), value.clone());
192                    evaluate_rete_ul_node(inner, &sub_facts)
193                })
194            }
195        }
196        ReteUlNode::UlAccumulate {
197            source_pattern,
198            extract_field,
199            source_conditions,
200            function,
201            ..
202        } => {
203            // Evaluate accumulate: collect matching facts and run function
204            use super::accumulate::*;
205
206            let pattern_prefix = format!("{}.", source_pattern);
207            let mut matching_values = Vec::new();
208
209            // Group facts by instance
210            let mut instances: std::collections::HashMap<String, std::collections::HashMap<String, String>> =
211                std::collections::HashMap::new();
212
213            for (key, value) in facts {
214                if key.starts_with(&pattern_prefix) {
215                    let parts: Vec<&str> = key.strip_prefix(&pattern_prefix).unwrap().split('.').collect();
216
217                    if parts.len() >= 2 {
218                        let instance_id = parts[0];
219                        let field_name = parts[1..].join(".");
220
221                        instances
222                            .entry(instance_id.to_string())
223                            .or_insert_with(std::collections::HashMap::new)
224                            .insert(field_name, value.clone());
225                    } else if parts.len() == 1 {
226                        instances
227                            .entry("default".to_string())
228                            .or_insert_with(std::collections::HashMap::new)
229                            .insert(parts[0].to_string(), value.clone());
230                    }
231                }
232            }
233
234            // Filter instances by source conditions
235            for (_instance_id, instance_facts) in instances {
236                let mut matches = true;
237
238                for condition_str in source_conditions {
239                    if !evaluate_condition_string(condition_str, &instance_facts) {
240                        matches = false;
241                        break;
242                    }
243                }
244
245                if matches {
246                    if let Some(value_str) = instance_facts.get(extract_field) {
247                        // Convert string to FactValue
248                        let fact_value = if let Ok(i) = value_str.parse::<i64>() {
249                            super::facts::FactValue::Integer(i)
250                        } else if let Ok(f) = value_str.parse::<f64>() {
251                            super::facts::FactValue::Float(f)
252                        } else if let Ok(b) = value_str.parse::<bool>() {
253                            super::facts::FactValue::Boolean(b)
254                        } else {
255                            super::facts::FactValue::String(value_str.clone())
256                        };
257                        matching_values.push(fact_value);
258                    }
259                }
260            }
261
262            // Run accumulate function - result determines if condition passes
263            let has_results = !matching_values.is_empty();
264
265            match function.as_str() {
266                "count" => has_results, // Count passes if there are any matches
267                "sum" | "average" | "min" | "max" => {
268                    // These functions need at least one value
269                    has_results
270                }
271                _ => true, // Unknown function - allow to continue
272            }
273        }
274        ReteUlNode::UlTerminal(_) => true // Rule match
275    }
276}
277
278/// RETE-UL: Unified Logic Node
279#[derive(Debug, Clone)]
280pub enum ReteUlNode {
281    UlAlpha(AlphaNode),
282    UlAnd(Box<ReteUlNode>, Box<ReteUlNode>),
283    UlOr(Box<ReteUlNode>, Box<ReteUlNode>),
284    UlNot(Box<ReteUlNode>),
285    UlExists(Box<ReteUlNode>),
286    UlForall(Box<ReteUlNode>),
287    UlAccumulate {
288        result_var: String,
289        source_pattern: String,
290        extract_field: String,
291        source_conditions: Vec<String>,
292        function: String,
293        function_arg: String,
294    },
295    UlTerminal(String), // Rule name
296}
297
298impl ReteUlNode {
299    /// Evaluate with typed facts (convenience method)
300    pub fn evaluate_typed(&self, facts: &super::facts::TypedFacts) -> bool {
301        evaluate_rete_ul_node_typed(self, facts)
302    }
303}
304
305/// RETE-UL Rule Struct
306pub struct ReteUlRule {
307    pub name: String,
308    pub node: ReteUlNode,
309    pub priority: i32,
310    pub no_loop: bool,
311    pub action: Box<dyn FnMut(&mut std::collections::HashMap<String, String>)>,
312}
313
314/// Drools-style RETE-UL rule firing loop
315/// Fires all matching rules, updates facts, repeats until no more rules can fire
316pub fn fire_rete_ul_rules(
317    rules: &mut [(String, ReteUlNode, Box<dyn FnMut(&mut std::collections::HashMap<String, String>)>)],
318    facts: &mut std::collections::HashMap<String, String>,
319) -> Vec<String> {
320    let mut fired_rules = Vec::new();
321    let mut changed = true;
322    while changed {
323        changed = false;
324        for (rule_name, node, action) in rules.iter_mut() {
325            let fired_flag = format!("{}_fired", rule_name);
326            if facts.get(&fired_flag) == Some(&"true".to_string()) {
327                continue;
328            }
329            if evaluate_rete_ul_node(node, facts) {
330                action(facts);
331                facts.insert(fired_flag.clone(), "true".to_string());
332                fired_rules.push(rule_name.clone());
333                changed = true;
334            }
335        }
336    }
337    fired_rules
338}
339
340/// Drools-style RETE-UL rule firing loop with agenda and control
341pub fn fire_rete_ul_rules_with_agenda(
342    rules: &mut [ReteUlRule],
343    facts: &mut std::collections::HashMap<String, String>,
344) -> Vec<String> {
345    let mut fired_rules = Vec::new();
346    let mut fired_flags = std::collections::HashSet::new();
347    let max_iterations = 100; // Prevent infinite loops
348    let mut iterations = 0;
349
350    loop {
351        iterations += 1;
352        if iterations > max_iterations {
353            eprintln!("Warning: RETE engine reached max iterations ({})", max_iterations);
354            break;
355        }
356
357        // Build agenda: rules that match and haven't been fired yet
358        let mut agenda: Vec<usize> = rules
359            .iter()
360            .enumerate()
361            .filter(|(_, rule)| {
362                // Check if rule already fired
363                if fired_flags.contains(&rule.name) {
364                    return false;
365                }
366                // Check if rule matches current facts
367                evaluate_rete_ul_node(&rule.node, facts)
368            })
369            .map(|(i, _)| i)
370            .collect();
371
372        // If no rules to fire, we're done
373        if agenda.is_empty() {
374            break;
375        }
376
377        // Sort agenda by priority (descending)
378        agenda.sort_by_key(|&i| -rules[i].priority);
379
380        // Fire all rules in agenda
381        for &i in &agenda {
382            let rule = &mut rules[i];
383
384            // Execute rule action
385            (rule.action)(facts);
386
387            // Mark as fired
388            fired_rules.push(rule.name.clone());
389            fired_flags.insert(rule.name.clone());
390
391            let fired_flag = format!("{}_fired", rule.name);
392            facts.insert(fired_flag, "true".to_string());
393        }
394
395        // If no_loop is enabled for all rules, stop after one iteration
396        if rules.iter().all(|r| r.no_loop) {
397            break;
398        }
399    }
400
401    fired_rules
402}
403
404/// RETE-UL Engine with cached nodes (Performance optimized!)
405/// This engine builds RETE nodes once and reuses them, avoiding expensive rebuilds
406pub struct ReteUlEngine {
407    rules: Vec<ReteUlRule>,
408    facts: std::collections::HashMap<String, String>,
409}
410
411impl ReteUlEngine {
412    /// Create new engine from Rule definitions (nodes are built and cached once)
413    pub fn new() -> Self {
414        Self {
415            rules: Vec::new(),
416            facts: std::collections::HashMap::new(),
417        }
418    }
419
420    /// Add a rule with custom action closure
421    pub fn add_rule_with_action<F>(
422        &mut self,
423        name: String,
424        node: ReteUlNode,
425        priority: i32,
426        no_loop: bool,
427        action: F,
428    ) where
429        F: FnMut(&mut std::collections::HashMap<String, String>) + 'static,
430    {
431        self.rules.push(ReteUlRule {
432            name,
433            node,
434            priority,
435            no_loop,
436            action: Box::new(action),
437        });
438    }
439
440    /// Add a rule from Rule definition (auto-build node once and cache)
441    pub fn add_rule_from_definition(
442        &mut self,
443        rule: &crate::rete::auto_network::Rule,
444        priority: i32,
445        no_loop: bool,
446    ) {
447        let node = build_rete_ul_from_condition_group(&rule.conditions);
448        let rule_name = rule.name.clone();
449
450        // Default action: just mark as fired
451        let action = Box::new(move |facts: &mut std::collections::HashMap<String, String>| {
452            facts.insert(format!("{}_executed", rule_name), "true".to_string());
453        }) as Box<dyn FnMut(&mut std::collections::HashMap<String, String>)>;
454
455        self.rules.push(ReteUlRule {
456            name: rule.name.clone(),
457            node,
458            priority,
459            no_loop,
460            action,
461        });
462    }
463
464    /// Set a fact
465    pub fn set_fact(&mut self, key: String, value: String) {
466        self.facts.insert(key, value);
467    }
468
469    /// Get a fact
470    pub fn get_fact(&self, key: &str) -> Option<&String> {
471        self.facts.get(key)
472    }
473
474    /// Remove a fact
475    pub fn remove_fact(&mut self, key: &str) -> Option<String> {
476        self.facts.remove(key)
477    }
478
479    /// Get all facts
480    pub fn get_all_facts(&self) -> &std::collections::HashMap<String, String> {
481        &self.facts
482    }
483
484    /// Clear all facts
485    pub fn clear_facts(&mut self) {
486        self.facts.clear();
487    }
488
489    /// Fire all rules with agenda (using cached nodes - NO rebuild!)
490    pub fn fire_all(&mut self) -> Vec<String> {
491        fire_rete_ul_rules_with_agenda(&mut self.rules, &mut self.facts)
492    }
493
494    /// Check if a specific rule matches current facts (without firing)
495    pub fn matches(&self, rule_name: &str) -> bool {
496        self.rules
497            .iter()
498            .find(|r| r.name == rule_name)
499            .map(|r| evaluate_rete_ul_node(&r.node, &self.facts))
500            .unwrap_or(false)
501    }
502
503    /// Get all matching rules (without firing)
504    pub fn get_matching_rules(&self) -> Vec<&str> {
505        self.rules
506            .iter()
507            .filter(|r| evaluate_rete_ul_node(&r.node, &self.facts))
508            .map(|r| r.name.as_str())
509            .collect()
510    }
511
512    /// Reset fired flags (allow rules to fire again)
513    pub fn reset_fired_flags(&mut self) {
514        let keys_to_remove: Vec<_> = self.facts
515            .keys()
516            .filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
517            .cloned()
518            .collect();
519        for key in keys_to_remove {
520            self.facts.remove(&key);
521        }
522    }
523}
524
525// ============================================================================
526// Typed Facts Support (NEW!)
527// ============================================================================
528
529use super::facts::{FactValue, TypedFacts};
530
531/// Evaluate RETE-UL node with typed facts (NEW!)
532pub fn evaluate_rete_ul_node_typed(node: &ReteUlNode, facts: &TypedFacts) -> bool {
533    match node {
534        ReteUlNode::UlAlpha(alpha) => {
535            alpha.matches_typed(facts)
536        }
537        ReteUlNode::UlAnd(left, right) => {
538            evaluate_rete_ul_node_typed(left, facts) && evaluate_rete_ul_node_typed(right, facts)
539        }
540        ReteUlNode::UlOr(left, right) => {
541            evaluate_rete_ul_node_typed(left, facts) || evaluate_rete_ul_node_typed(right, facts)
542        }
543        ReteUlNode::UlNot(inner) => {
544            !evaluate_rete_ul_node_typed(inner, facts)
545        }
546        ReteUlNode::UlExists(inner) => {
547            let target_field = match &**inner {
548                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
549                _ => "".to_string(),
550            };
551            if target_field.contains('.') {
552                let parts: Vec<&str> = target_field.split('.').collect();
553                if parts.len() == 2 {
554                    let prefix = parts[0];
555                    let suffix = parts[1];
556                    let filtered: Vec<_> = facts.get_all().iter()
557                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
558                        .collect();
559                    filtered.iter().any(|(_, _)| {
560                        evaluate_rete_ul_node_typed(inner, facts)
561                    })
562                } else {
563                    evaluate_rete_ul_node_typed(inner, facts)
564                }
565            } else {
566                evaluate_rete_ul_node_typed(inner, facts)
567            }
568        }
569        ReteUlNode::UlForall(inner) => {
570            let target_field = match &**inner {
571                ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
572                _ => "".to_string(),
573            };
574            if target_field.contains('.') {
575                let parts: Vec<&str> = target_field.split('.').collect();
576                if parts.len() == 2 {
577                    let prefix = parts[0];
578                    let suffix = parts[1];
579                    let filtered: Vec<_> = facts.get_all().iter()
580                        .filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
581                        .collect();
582                    if filtered.is_empty() {
583                        return true; // Vacuous truth
584                    }
585                    filtered.iter().all(|(_, _)| {
586                        evaluate_rete_ul_node_typed(inner, facts)
587                    })
588                } else {
589                    if facts.get_all().is_empty() {
590                        return true; // Vacuous truth
591                    }
592                    evaluate_rete_ul_node_typed(inner, facts)
593                }
594            } else {
595                if facts.get_all().is_empty() {
596                    return true; // Vacuous truth
597                }
598                evaluate_rete_ul_node_typed(inner, facts)
599            }
600        }
601        ReteUlNode::UlAccumulate {
602            source_pattern,
603            extract_field,
604            source_conditions,
605            function,
606            ..
607        } => {
608            // Evaluate accumulate with typed facts
609            use super::accumulate::*;
610
611            let pattern_prefix = format!("{}.", source_pattern);
612            let mut matching_values = Vec::new();
613
614            // Group facts by instance
615            let mut instances: std::collections::HashMap<String, std::collections::HashMap<String, FactValue>> =
616                std::collections::HashMap::new();
617
618            for (key, value) in facts.get_all() {
619                if key.starts_with(&pattern_prefix) {
620                    let parts: Vec<&str> = key.strip_prefix(&pattern_prefix).unwrap().split('.').collect();
621
622                    if parts.len() >= 2 {
623                        let instance_id = parts[0];
624                        let field_name = parts[1..].join(".");
625
626                        instances
627                            .entry(instance_id.to_string())
628                            .or_insert_with(std::collections::HashMap::new)
629                            .insert(field_name, value.clone());
630                    } else if parts.len() == 1 {
631                        instances
632                            .entry("default".to_string())
633                            .or_insert_with(std::collections::HashMap::new)
634                            .insert(parts[0].to_string(), value.clone());
635                    }
636                }
637            }
638
639            // Filter instances by source conditions
640            for (_instance_id, instance_facts) in instances {
641                let mut matches = true;
642
643                for condition_str in source_conditions {
644                    // Convert FactValues to strings for condition evaluation
645                    let string_facts: HashMap<String, String> = instance_facts
646                        .iter()
647                        .map(|(k, v)| (k.clone(), format!("{:?}", v)))
648                        .collect();
649
650                    if !evaluate_condition_string(condition_str, &string_facts) {
651                        matches = false;
652                        break;
653                    }
654                }
655
656                if matches {
657                    if let Some(value) = instance_facts.get(extract_field) {
658                        matching_values.push(value.clone());
659                    }
660                }
661            }
662
663            // Run accumulate function - result determines if condition passes
664            let has_results = !matching_values.is_empty();
665
666            match function.as_str() {
667                "count" => has_results,
668                "sum" | "average" | "min" | "max" => has_results,
669                _ => true,
670            }
671        }
672        ReteUlNode::UlTerminal(_) => true
673    }
674}
675
676/// Typed RETE-UL Rule
677pub struct TypedReteUlRule {
678    pub name: String,
679    pub node: ReteUlNode,
680    pub priority: i32,
681    pub no_loop: bool,
682    pub action: Box<dyn FnMut(&mut TypedFacts)>,
683}
684
685/// Typed RETE-UL Engine with cached nodes (Performance + Type Safety!)
686/// This is the recommended engine for new code
687pub struct TypedReteUlEngine {
688    rules: Vec<TypedReteUlRule>,
689    facts: TypedFacts,
690}
691
692impl TypedReteUlEngine {
693    /// Create new typed engine
694    pub fn new() -> Self {
695        Self {
696            rules: Vec::new(),
697            facts: TypedFacts::new(),
698        }
699    }
700
701    /// Add a rule with custom action
702    pub fn add_rule_with_action<F>(
703        &mut self,
704        name: String,
705        node: ReteUlNode,
706        priority: i32,
707        no_loop: bool,
708        action: F,
709    ) where
710        F: FnMut(&mut TypedFacts) + 'static,
711    {
712        self.rules.push(TypedReteUlRule {
713            name,
714            node,
715            priority,
716            no_loop,
717            action: Box::new(action),
718        });
719    }
720
721    /// Add a rule from Rule definition
722    pub fn add_rule_from_definition(
723        &mut self,
724        rule: &crate::rete::auto_network::Rule,
725        priority: i32,
726        no_loop: bool,
727    ) {
728        let node = build_rete_ul_from_condition_group(&rule.conditions);
729        let rule_name = rule.name.clone();
730
731        let action = Box::new(move |facts: &mut TypedFacts| {
732            facts.set(format!("{}_executed", rule_name), true);
733        }) as Box<dyn FnMut(&mut TypedFacts)>;
734
735        self.rules.push(TypedReteUlRule {
736            name: rule.name.clone(),
737            node,
738            priority,
739            no_loop,
740            action,
741        });
742    }
743
744    /// Set a fact with typed value
745    pub fn set_fact<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
746        self.facts.set(key, value);
747    }
748
749    /// Get a fact
750    pub fn get_fact(&self, key: &str) -> Option<&FactValue> {
751        self.facts.get(key)
752    }
753
754    /// Remove a fact
755    pub fn remove_fact(&mut self, key: &str) -> Option<FactValue> {
756        self.facts.remove(key)
757    }
758
759    /// Get all facts
760    pub fn get_all_facts(&self) -> &TypedFacts {
761        &self.facts
762    }
763
764    /// Clear all facts
765    pub fn clear_facts(&mut self) {
766        self.facts.clear();
767    }
768
769    /// Fire all rules with agenda (using cached nodes + typed evaluation!)
770    pub fn fire_all(&mut self) -> Vec<String> {
771        let mut fired_rules = Vec::new();
772        let mut agenda: Vec<usize>;
773        let mut changed = true;
774        let mut fired_flags = std::collections::HashSet::new();
775
776        while changed {
777            changed = false;
778
779            // Build agenda: rules that match and not fired
780            agenda = self.rules.iter().enumerate()
781                .filter(|(_, rule)| {
782                    let fired_flag = format!("{}_fired", rule.name);
783                    let already_fired = fired_flags.contains(&rule.name) ||
784                        self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
785                    !rule.no_loop || !already_fired
786                })
787                .filter(|(_, rule)| evaluate_rete_ul_node_typed(&rule.node, &self.facts))
788                .map(|(i, _)| i)
789                .collect();
790
791            // Sort by priority (descending)
792            agenda.sort_by_key(|&i| -self.rules[i].priority);
793
794            for &i in &agenda {
795                let rule = &mut self.rules[i];
796                let fired_flag = format!("{}_fired", rule.name);
797                let already_fired = fired_flags.contains(&rule.name) ||
798                    self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
799
800                if rule.no_loop && already_fired {
801                    continue;
802                }
803
804                (rule.action)(&mut self.facts);
805                fired_rules.push(rule.name.clone());
806                fired_flags.insert(rule.name.clone());
807                self.facts.set(fired_flag, true);
808                changed = true;
809            }
810        }
811
812        fired_rules
813    }
814
815    /// Check if a specific rule matches current facts
816    pub fn matches(&self, rule_name: &str) -> bool {
817        self.rules
818            .iter()
819            .find(|r| r.name == rule_name)
820            .map(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts))
821            .unwrap_or(false)
822    }
823
824    /// Get all matching rules
825    pub fn get_matching_rules(&self) -> Vec<&str> {
826        self.rules
827            .iter()
828            .filter(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts))
829            .map(|r| r.name.as_str())
830            .collect()
831    }
832
833    /// Reset fired flags
834    pub fn reset_fired_flags(&mut self) {
835        let keys_to_remove: Vec<_> = self.facts.get_all()
836            .keys()
837            .filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
838            .cloned()
839            .collect();
840        for key in keys_to_remove {
841            self.facts.remove(&key);
842        }
843    }
844}
845
846impl Default for TypedReteUlEngine {
847    fn default() -> Self {
848        Self::new()
849    }
850}
851