Skip to main content

rust_rule_engine/rete/
propagation.rs

1//! Incremental Propagation Engine (P3 Feature - Advanced)
2//!
3//! This module implements incremental updates similar to Drools:
4//! - Only propagate changed facts through the network
5//! - Track affected rules and activations
6//! - Efficient re-evaluation after updates
7
8use super::agenda::{Activation, AdvancedAgenda};
9use super::deffacts::DeffactsRegistry;
10use super::facts::{FactValue, TypedFacts};
11use super::globals::GlobalsRegistry;
12use super::network::TypedReteUlRule;
13use super::template::TemplateRegistry;
14use super::tms::TruthMaintenanceSystem;
15use super::working_memory::{FactHandle, WorkingMemory};
16use crate::errors::{Result, RuleEngineError};
17use std::collections::{HashMap, HashSet};
18use std::sync::Arc;
19
20/// Track which rules are affected by which fact types
21#[derive(Debug)]
22pub struct RuleDependencyGraph {
23    /// Map: fact_type -> set of rule indices that depend on it
24    fact_type_to_rules: HashMap<String, HashSet<usize>>,
25    /// Map: rule index -> set of fact types it depends on
26    rule_to_fact_types: HashMap<usize, HashSet<String>>,
27}
28
29impl RuleDependencyGraph {
30    /// Create new dependency graph
31    pub fn new() -> Self {
32        Self {
33            fact_type_to_rules: HashMap::new(),
34            rule_to_fact_types: HashMap::new(),
35        }
36    }
37
38    /// Add dependency: rule depends on fact type
39    pub fn add_dependency(&mut self, rule_idx: usize, fact_type: String) {
40        self.fact_type_to_rules
41            .entry(fact_type.clone())
42            .or_default()
43            .insert(rule_idx);
44
45        self.rule_to_fact_types
46            .entry(rule_idx)
47            .or_default()
48            .insert(fact_type);
49    }
50
51    /// Get rules affected by a fact type change
52    pub fn get_affected_rules(&self, fact_type: &str) -> HashSet<usize> {
53        self.fact_type_to_rules
54            .get(fact_type)
55            .cloned()
56            .unwrap_or_else(HashSet::new)
57    }
58
59    /// Get fact types that a rule depends on
60    pub fn get_rule_dependencies(&self, rule_idx: usize) -> HashSet<String> {
61        self.rule_to_fact_types
62            .get(&rule_idx)
63            .cloned()
64            .unwrap_or_else(HashSet::new)
65    }
66}
67
68impl Default for RuleDependencyGraph {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74/// Type alias for custom test functions in RETE engine
75/// Functions take a slice of FactValues and return a FactValue (typically Boolean)
76pub type ReteCustomFunction =
77    Arc<dyn Fn(&[FactValue], &TypedFacts) -> Result<FactValue> + Send + Sync>;
78
79/// Incremental Propagation Engine
80/// Only re-evaluates rules affected by changed facts
81pub struct IncrementalEngine {
82    /// Working memory
83    working_memory: WorkingMemory,
84    /// Rules
85    rules: Vec<TypedReteUlRule>,
86    /// Dependency graph
87    dependencies: RuleDependencyGraph,
88    /// Advanced agenda
89    agenda: AdvancedAgenda,
90    /// Track which facts each rule last matched
91    rule_matched_facts: HashMap<usize, HashSet<FactHandle>>,
92    /// Template registry for type-safe facts
93    templates: TemplateRegistry,
94    /// Global variables registry
95    globals: GlobalsRegistry,
96    /// Deffacts registry for initial facts
97    deffacts: DeffactsRegistry,
98    /// Custom functions for Test CE support
99    custom_functions: HashMap<String, ReteCustomFunction>,
100    /// Truth Maintenance System
101    tms: TruthMaintenanceSystem,
102}
103
104impl IncrementalEngine {
105    /// Create new incremental engine
106    pub fn new() -> Self {
107        Self {
108            working_memory: WorkingMemory::new(),
109            rules: Vec::new(),
110            dependencies: RuleDependencyGraph::new(),
111            agenda: AdvancedAgenda::new(),
112            rule_matched_facts: HashMap::new(),
113            custom_functions: HashMap::new(),
114            templates: TemplateRegistry::new(),
115            globals: GlobalsRegistry::new(),
116            deffacts: DeffactsRegistry::new(),
117            tms: TruthMaintenanceSystem::new(),
118        }
119    }
120
121    /// Add rule and register its dependencies
122    pub fn add_rule(&mut self, rule: TypedReteUlRule, depends_on: Vec<String>) {
123        let rule_idx = self.rules.len();
124
125        // Register dependencies
126        for fact_type in depends_on {
127            self.dependencies.add_dependency(rule_idx, fact_type);
128        }
129
130        self.rules.push(rule);
131    }
132
133    /// Insert fact into working memory
134    pub fn insert(&mut self, fact_type: String, data: TypedFacts) -> FactHandle {
135        let handle = self.working_memory.insert(fact_type.clone(), data);
136
137        // Default: Treat as explicit assertion (backward compatible)
138        self.tms.add_explicit_justification(handle);
139
140        // Trigger incremental propagation for this fact type
141        self.propagate_changes_for_type(&fact_type);
142
143        handle
144    }
145
146    /// Update fact in working memory
147    pub fn update(&mut self, handle: FactHandle, data: TypedFacts) -> Result<()> {
148        // Get fact type before update
149        let fact_type = self
150            .working_memory
151            .get(&handle)
152            .map(|f| f.fact_type.clone())
153            .ok_or_else(|| RuleEngineError::FieldNotFound {
154                field: format!("FactHandle {} not found", handle),
155            })?;
156
157        self.working_memory
158            .update(handle, data)
159            .map_err(|e| RuleEngineError::EvaluationError { message: e })?;
160
161        // Trigger incremental propagation for this fact type
162        self.propagate_changes_for_type(&fact_type);
163
164        Ok(())
165    }
166
167    /// Retract fact from working memory
168    pub fn retract(&mut self, handle: FactHandle) -> Result<()> {
169        // Get fact type before retract
170        let fact_type = self
171            .working_memory
172            .get(&handle)
173            .map(|f| f.fact_type.clone())
174            .ok_or_else(|| RuleEngineError::FieldNotFound {
175                field: format!("FactHandle {} not found", handle),
176            })?;
177
178        self.working_memory
179            .retract(handle)
180            .map_err(|e| RuleEngineError::EvaluationError { message: e })?;
181
182        // TMS: Handle cascade retraction
183        let cascaded_facts = self.tms.retract_with_cascade(handle);
184
185        // Actually retract cascaded facts from working memory
186        for cascaded_handle in cascaded_facts {
187            if let Ok(fact_type) = self
188                .working_memory
189                .get(&cascaded_handle)
190                .map(|f| f.fact_type.clone())
191                .ok_or_else(|| RuleEngineError::FieldNotFound {
192                    field: format!("FactHandle {} not found", cascaded_handle),
193                })
194            {
195                let _ = self.working_memory.retract(cascaded_handle);
196                // Propagate for each cascaded fact
197                self.propagate_changes_for_type(&fact_type);
198            }
199        }
200
201        // Trigger incremental propagation for this fact type
202        self.propagate_changes_for_type(&fact_type);
203
204        Ok(())
205    }
206
207    /// Insert a fact with explicit assertion (user provided)
208    /// This fact will NOT be auto-retracted by TMS
209    pub fn insert_explicit(&mut self, fact_type: String, data: TypedFacts) -> FactHandle {
210        let handle = self.working_memory.insert(fact_type.clone(), data);
211
212        // Add explicit justification in TMS
213        self.tms.add_explicit_justification(handle);
214
215        // Trigger incremental propagation for this fact type
216        self.propagate_changes_for_type(&fact_type);
217
218        handle
219    }
220
221    /// Insert a fact with logical assertion (derived by a rule)
222    /// This fact WILL be auto-retracted if its premises become invalid
223    ///
224    /// # Arguments
225    /// * `fact_type` - Type of the fact (e.g., "Customer")
226    /// * `data` - The fact data
227    /// * `source_rule` - Name of the rule deriving this fact
228    /// * `premise_handles` - Handles of facts matched in the rule's WHEN clause
229    pub fn insert_logical(
230        &mut self,
231        fact_type: String,
232        data: TypedFacts,
233        source_rule: String,
234        premise_handles: Vec<FactHandle>,
235    ) -> FactHandle {
236        let handle = self.working_memory.insert(fact_type.clone(), data);
237
238        // Add logical justification in TMS
239        self.tms
240            .add_logical_justification(handle, source_rule, premise_handles);
241
242        // Trigger incremental propagation for this fact type
243        self.propagate_changes_for_type(&fact_type);
244
245        handle
246    }
247
248    /// Resolve premise keys (format: "Type.field=value" or "Type.field=")
249    /// to a Vec<FactHandle> by looking up facts of the given type and matching
250    /// the field value when provided. If value is empty, return the most recent
251    /// handle for that type (if any).
252    pub fn resolve_premise_keys(&self, premise_keys: Vec<String>) -> Vec<FactHandle> {
253        let mut handles = Vec::new();
254
255        for key in premise_keys {
256            // Split into type.field=value
257            if let Some(eq_pos) = key.find('=') {
258                let left = &key[..eq_pos];
259                let value_part = &key[eq_pos + 1..];
260
261                if let Some(dot_pos) = left.find('.') {
262                    let fact_type = &left[..dot_pos];
263                    let field = &left[dot_pos + 1..];
264
265                    // Search facts of this type
266                    let facts = self.working_memory.get_by_type(fact_type);
267                    // If value_part is empty, pick last handle if any
268                    if value_part.is_empty() {
269                        // Prefer the most recent non-retracted fact for this type
270                        if let Some(fact) = facts.iter().rev().find(|f| !f.metadata.retracted) {
271                            handles.push(fact.handle);
272                            continue;
273                        }
274                    } else {
275                        // Try to match provided value text against the field in TypedFacts
276                        // Parse the provided value into a FactValue-like expectation so we
277                        // can compare numbers/booleans properly instead of relying on string equality.
278                        fn parse_literal(s: &str) -> super::facts::FactValue {
279                            let s = s.trim();
280                            if s == "true" {
281                                return super::facts::FactValue::Boolean(true);
282                            }
283                            if s == "false" {
284                                return super::facts::FactValue::Boolean(false);
285                            }
286                            // Quoted string
287                            if (s.starts_with('"') && s.ends_with('"'))
288                                || (s.starts_with('\'') && s.ends_with('\''))
289                            {
290                                return super::facts::FactValue::String(
291                                    s[1..s.len() - 1].to_string(),
292                                );
293                            }
294                            // Integer
295                            if let Ok(i) = s.parse::<i64>() {
296                                return super::facts::FactValue::Integer(i);
297                            }
298                            // Float
299                            if let Ok(f) = s.parse::<f64>() {
300                                return super::facts::FactValue::Float(f);
301                            }
302
303                            // Fallback to string
304                            super::facts::FactValue::String(s.to_string())
305                        }
306
307                        fn fact_value_equal(
308                            a: &super::facts::FactValue,
309                            b: &super::facts::FactValue,
310                        ) -> bool {
311                            use super::facts::FactValue;
312                            match (a, b) {
313                                (FactValue::Boolean(x), FactValue::Boolean(y)) => x == y,
314                                (FactValue::Integer(x), FactValue::Integer(y)) => x == y,
315                                (FactValue::Float(x), FactValue::Float(y)) => (x - y).abs() < 1e-9,
316                                // Number cross-type comparisons
317                                (FactValue::Integer(x), FactValue::Float(y)) => {
318                                    ((*x as f64) - *y).abs() < 1e-9
319                                }
320                                (FactValue::Float(x), FactValue::Integer(y)) => {
321                                    (*x - (*y as f64)).abs() < 1e-9
322                                }
323                                (FactValue::String(x), FactValue::String(y)) => x == y,
324                                // Mixed string vs other: compare stringified forms
325                                _ => a.as_str() == b.as_str(),
326                            }
327                        }
328
329                        let expected = parse_literal(value_part);
330
331                        // Prefer the most recent non-retracted matching fact for determinism
332                        if let Some(fact) = facts.iter().rev().find(|fact| {
333                            if fact.metadata.retracted {
334                                return false;
335                            }
336                            if let Some(fv) = fact.data.get(field) {
337                                fact_value_equal(fv, &expected) || fv.as_str() == value_part
338                            } else {
339                                false
340                            }
341                        }) {
342                            handles.push(fact.handle);
343                        }
344                    }
345                }
346            }
347        }
348
349        handles
350    }
351
352    /// Get TMS reference
353    pub fn tms(&self) -> &TruthMaintenanceSystem {
354        &self.tms
355    }
356
357    /// Get mutable TMS reference
358    pub fn tms_mut(&mut self) -> &mut TruthMaintenanceSystem {
359        &mut self.tms
360    }
361
362    /// Propagate changes for a specific fact type (incremental!)
363    fn propagate_changes_for_type(&mut self, fact_type: &str) {
364        // Get affected rules
365        let affected_rules = self.dependencies.get_affected_rules(fact_type);
366
367        if affected_rules.is_empty() {
368            return; // No rules depend on this fact type
369        }
370
371        // Get facts of this type
372        let facts_of_type = self.working_memory.get_by_type(fact_type);
373
374        // Re-evaluate only affected rules, checking each fact individually
375        for &rule_idx in &affected_rules {
376            let rule = &self.rules[rule_idx];
377
378            // Check each fact of this type against the rule
379            for fact in &facts_of_type {
380                // Create TypedFacts for just this fact
381                let mut single_fact_data = TypedFacts::new();
382                for (key, value) in fact.data.get_all() {
383                    single_fact_data.set(format!("{}.{}", fact_type, key), value.clone());
384                }
385                // Store handle for Retract action
386                single_fact_data.set_fact_handle(fact_type.to_string(), fact.handle);
387
388                // Evaluate rule condition with this single fact
389                let matches = super::network::evaluate_rete_ul_node_typed(
390                    &rule.node,
391                    &single_fact_data,
392                    &self.custom_functions,
393                );
394
395                if matches {
396                    // Create activation for this specific fact match
397                    let activation = Activation::new(rule.name.clone(), rule.priority)
398                        .with_no_loop(rule.no_loop)
399                        .with_matched_fact(fact.handle);
400
401                    self.agenda.add_activation(activation);
402                }
403            }
404        }
405    }
406
407    /// Propagate changes for all fact types (re-evaluate all rules)
408    fn propagate_changes(&mut self) {
409        // Get all fact types
410        let fact_types: Vec<String> = self
411            .working_memory
412            .get_all_facts()
413            .iter()
414            .map(|f| f.fact_type.clone())
415            .collect::<std::collections::HashSet<_>>()
416            .into_iter()
417            .collect();
418
419        // Evaluate each fact type using per-fact evaluation
420        for fact_type in fact_types {
421            let facts_of_type = self.working_memory.get_by_type(&fact_type);
422
423            for rule in self.rules.iter() {
424                // Skip if rule has no-loop and already fired
425                if rule.no_loop && self.agenda.has_fired(&rule.name) {
426                    continue;
427                }
428
429                // Check each fact against the rule
430                for fact in &facts_of_type {
431                    let mut single_fact_data = TypedFacts::new();
432                    for (key, value) in fact.data.get_all() {
433                        single_fact_data.set(format!("{}.{}", fact_type, key), value.clone());
434                    }
435
436                    let matches = super::network::evaluate_rete_ul_node_typed(
437                        &rule.node,
438                        &single_fact_data,
439                        &self.custom_functions,
440                    );
441
442                    if matches {
443                        let activation = Activation::new(rule.name.clone(), rule.priority)
444                            .with_no_loop(rule.no_loop)
445                            .with_matched_fact(fact.handle);
446
447                        self.agenda.add_activation(activation);
448                    }
449                }
450            }
451        }
452    }
453
454    /// Fire all pending activations
455    pub fn fire_all(&mut self) -> Vec<String> {
456        let mut fired_rules = Vec::new();
457        let max_iterations = 1000; // Prevent infinite loops
458        let mut iteration_count = 0;
459
460        while let Some(activation) = self.agenda.get_next_activation() {
461            iteration_count += 1;
462            if iteration_count > max_iterations {
463                eprintln!("WARNING: Maximum iterations ({}) reached in fire_all(). Possible infinite loop!", max_iterations);
464                break;
465            }
466
467            // Find rule
468            if let Some((_idx, rule)) = self
469                .rules
470                .iter_mut()
471                .enumerate()
472                .find(|(_, r)| r.name == activation.rule_name)
473            {
474                // Validate that matched fact still exists (hasn't been retracted)
475                if let Some(matched_handle) = activation.matched_fact_handle {
476                    if self.working_memory.get(&matched_handle).is_none() {
477                        // Fact was retracted, skip this activation
478                        continue;
479                    }
480                }
481
482                // Execute action on a copy of all facts
483                let original_facts = self.working_memory.to_typed_facts();
484                let mut modified_facts = original_facts.clone();
485
486                // Inject matched fact handle if available
487                if let Some(matched_handle) = activation.matched_fact_handle {
488                    // Find the fact type from working memory
489                    if let Some(fact) = self.working_memory.get(&matched_handle) {
490                        modified_facts.set_fact_handle(fact.fact_type.clone(), matched_handle);
491                    }
492                }
493
494                let mut action_results = super::ActionResults::new();
495                (rule.action)(&mut modified_facts, &mut action_results);
496
497                // Update working memory: detect changes and apply them
498                // Build a map of fact_type -> updated fields
499                let mut updates_by_type: HashMap<String, Vec<(String, FactValue)>> = HashMap::new();
500
501                for (key, value) in modified_facts.get_all() {
502                    // Keys are in format "FactType.field" or "FactType.handle.field"
503                    // We want to extract the FactType and field name
504                    if let Some(original_value) = original_facts.get(key) {
505                        if original_value != value {
506                            // Value changed! Extract fact type and field
507                            let parts: Vec<&str> = key.split('.').collect();
508                            if parts.len() >= 2 {
509                                let fact_type = parts[0].to_string();
510                                // Field is the last part (skip handle if present)
511                                let field = if parts.len() == 2 {
512                                    parts[1].to_string()
513                                } else {
514                                    parts[parts.len() - 1].to_string()
515                                };
516
517                                updates_by_type
518                                    .entry(fact_type)
519                                    .or_default()
520                                    .push((field, value.clone()));
521                            }
522                        }
523                    } else {
524                        // New field added
525                        let parts: Vec<&str> = key.split('.').collect();
526                        if parts.len() >= 2 {
527                            let fact_type = parts[0].to_string();
528                            let field = if parts.len() == 2 {
529                                parts[1].to_string()
530                            } else {
531                                parts[parts.len() - 1].to_string()
532                            };
533
534                            updates_by_type
535                                .entry(fact_type)
536                                .or_default()
537                                .push((field, value.clone()));
538                        }
539                    }
540                }
541
542                // Apply updates to working memory facts
543                for (fact_type, field_updates) in updates_by_type {
544                    // Get handles of all facts of this type (collect to avoid borrow issues)
545                    let fact_handles: Vec<FactHandle> = self
546                        .working_memory
547                        .get_by_type(&fact_type)
548                        .iter()
549                        .map(|f| f.handle)
550                        .collect();
551
552                    for handle in fact_handles {
553                        if let Some(fact) = self.working_memory.get(&handle) {
554                            let mut updated_data = fact.data.clone();
555
556                            // Apply all field updates
557                            for (field, value) in &field_updates {
558                                updated_data.set(field, value.clone());
559                            }
560
561                            let _ = self.working_memory.update(handle, updated_data);
562                        }
563                    }
564                }
565
566                // Re-evaluate matches after working memory update
567                // This allows subsequent rules to see the updated values
568                self.propagate_changes();
569
570                // Process action results (retractions, agenda activations, etc.)
571                self.process_action_results(action_results);
572
573                // Track fired rule
574                fired_rules.push(activation.rule_name.clone());
575                self.agenda.mark_rule_fired(&activation);
576            }
577        }
578
579        fired_rules
580    }
581
582    /// Process action results from rule execution
583    fn process_action_results(&mut self, results: super::ActionResults) {
584        for result in results.results {
585            match result {
586                super::ActionResult::Retract(handle) => {
587                    // Retract fact by handle
588                    if let Err(e) = self.retract(handle) {
589                        eprintln!("❌ Failed to retract fact {:?}: {}", handle, e);
590                    }
591                }
592                super::ActionResult::RetractByType(fact_type) => {
593                    // Retract first fact of this type
594                    let facts_of_type = self.working_memory.get_by_type(&fact_type);
595                    if let Some(fact) = facts_of_type.first() {
596                        let handle = fact.handle;
597                        if let Err(e) = self.retract(handle) {
598                            eprintln!("❌ Failed to retract fact {:?}: {}", handle, e);
599                        }
600                    }
601                }
602                super::ActionResult::Update(handle) => {
603                    // Re-evaluate rules that depend on this fact type
604                    if let Some(fact) = self.working_memory.get(&handle) {
605                        let fact_type = fact.fact_type.clone();
606                        self.propagate_changes_for_type(&fact_type);
607                    }
608                }
609                super::ActionResult::ActivateAgendaGroup(group) => {
610                    // Activate agenda group
611                    self.agenda.set_focus(group);
612                }
613                super::ActionResult::InsertFact { fact_type, data } => {
614                    // Insert new explicit fact
615                    self.insert_explicit(fact_type, data);
616                }
617                super::ActionResult::InsertLogicalFact {
618                    fact_type,
619                    data,
620                    rule_name,
621                    premises,
622                } => {
623                    // Insert new logical fact
624                    let _handle = self.insert_logical(fact_type, data, rule_name, premises);
625                }
626                super::ActionResult::CallFunction {
627                    function_name,
628                    args,
629                } => {
630                    // Try to execute function if registered
631                    if let Some(func) = self.custom_functions.get(&function_name) {
632                        // Convert string args to FactValues
633                        let fact_values: Vec<FactValue> =
634                            args.iter().map(|s| FactValue::String(s.clone())).collect();
635
636                        // Execute function (ignore return value for actions)
637                        let all_facts = self.working_memory.to_typed_facts();
638                        match func(&fact_values, &all_facts) {
639                            Ok(_) => println!("✅ Called function: {}", function_name),
640                            Err(e) => eprintln!("❌ Function {} failed: {}", function_name, e),
641                        }
642                    } else {
643                        // Function not registered, just log
644                        println!("🔧 Function call queued: {}({:?})", function_name, args);
645                    }
646                }
647                super::ActionResult::ScheduleRule {
648                    rule_name,
649                    delay_ms,
650                } => {
651                    // Log scheduled rules (requires scheduler to actually execute)
652                    println!("⏰ Rule scheduled: {} after {}ms", rule_name, delay_ms);
653                    // TODO: Implement rule scheduler
654                }
655                super::ActionResult::None => {
656                    // No action needed
657                }
658            }
659        }
660    }
661
662    /// Get working memory
663    pub fn working_memory(&self) -> &WorkingMemory {
664        &self.working_memory
665    }
666
667    /// Get mutable working memory
668    pub fn working_memory_mut(&mut self) -> &mut WorkingMemory {
669        &mut self.working_memory
670    }
671
672    /// Get agenda
673    pub fn agenda(&self) -> &AdvancedAgenda {
674        &self.agenda
675    }
676
677    /// Get mutable agenda
678    pub fn agenda_mut(&mut self) -> &mut AdvancedAgenda {
679        &mut self.agenda
680    }
681
682    /// Set conflict resolution strategy
683    ///
684    /// Controls how conflicting rules in the agenda are ordered.
685    /// Available strategies: Salience (default), LEX, MEA, Depth, Breadth, Simplicity, Complexity, Random
686    pub fn set_conflict_resolution_strategy(
687        &mut self,
688        strategy: super::agenda::ConflictResolutionStrategy,
689    ) {
690        self.agenda.set_strategy(strategy);
691    }
692
693    /// Get current conflict resolution strategy
694    pub fn conflict_resolution_strategy(&self) -> super::agenda::ConflictResolutionStrategy {
695        self.agenda.strategy()
696    }
697
698    /// Get statistics
699    pub fn stats(&self) -> IncrementalEngineStats {
700        IncrementalEngineStats {
701            rules: self.rules.len(),
702            working_memory: self.working_memory.stats(),
703            agenda: self.agenda.stats(),
704            dependencies: self.dependencies.fact_type_to_rules.len(),
705        }
706    }
707
708    /// Clear fired flags and reset agenda
709    pub fn reset(&mut self) {
710        self.agenda.reset_fired_flags();
711    }
712
713    /// Get template registry
714    pub fn templates(&self) -> &TemplateRegistry {
715        &self.templates
716    }
717
718    /// Get mutable template registry
719    pub fn templates_mut(&mut self) -> &mut TemplateRegistry {
720        &mut self.templates
721    }
722
723    /// Register a custom function for Test CE support
724    ///
725    /// # Example
726    /// ```
727    /// use rust_rule_engine::rete::{IncrementalEngine, FactValue};
728    ///
729    /// let mut engine = IncrementalEngine::new();
730    /// engine.register_function(
731    ///     "is_valid_email",
732    ///     |args, _facts| {
733    ///         if let Some(FactValue::String(email)) = args.first() {
734    ///             Ok(FactValue::Boolean(email.contains('@')))
735    ///         } else {
736    ///             Ok(FactValue::Boolean(false))
737    ///         }
738    ///     }
739    /// );
740    /// ```
741    pub fn register_function<F>(&mut self, name: &str, func: F)
742    where
743        F: Fn(&[FactValue], &TypedFacts) -> Result<FactValue> + Send + Sync + 'static,
744    {
745        self.custom_functions
746            .insert(name.to_string(), Arc::new(func));
747    }
748
749    /// Get a custom function by name (for Test CE evaluation)
750    pub fn get_function(&self, name: &str) -> Option<&ReteCustomFunction> {
751        self.custom_functions.get(name)
752    }
753
754    /// Get global variables registry
755    pub fn globals(&self) -> &GlobalsRegistry {
756        &self.globals
757    }
758
759    /// Get mutable global variables registry
760    pub fn globals_mut(&mut self) -> &mut GlobalsRegistry {
761        &mut self.globals
762    }
763
764    /// Get deffacts registry
765    pub fn deffacts(&self) -> &DeffactsRegistry {
766        &self.deffacts
767    }
768
769    /// Get mutable deffacts registry
770    pub fn deffacts_mut(&mut self) -> &mut DeffactsRegistry {
771        &mut self.deffacts
772    }
773
774    /// Load all registered deffacts into working memory
775    /// Returns handles of all inserted facts
776    pub fn load_deffacts(&mut self) -> Vec<FactHandle> {
777        let mut handles = Vec::new();
778
779        // Get all facts from all registered deffacts
780        let all_facts = self.deffacts.get_all_facts();
781
782        for (_deffacts_name, fact_instance) in all_facts {
783            // Check if template exists for this fact type
784            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
785                // Use template validation
786                match self.insert_with_template(&fact_instance.fact_type, fact_instance.data) {
787                    Ok(h) => h,
788                    Err(_) => continue, // Skip invalid facts
789                }
790            } else {
791                // Insert without template validation
792                self.insert(fact_instance.fact_type, fact_instance.data)
793            };
794
795            handles.push(handle);
796        }
797
798        handles
799    }
800
801    /// Load a specific deffacts set by name
802    /// Returns handles of inserted facts or error if deffacts not found
803    pub fn load_deffacts_by_name(&mut self, name: &str) -> crate::errors::Result<Vec<FactHandle>> {
804        // Clone the facts to avoid borrow checker issues
805        let facts_to_insert = {
806            let deffacts = self.deffacts.get(name).ok_or_else(|| {
807                crate::errors::RuleEngineError::EvaluationError {
808                    message: format!("Deffacts '{}' not found", name),
809                }
810            })?;
811            deffacts.facts.clone()
812        };
813
814        let mut handles = Vec::new();
815
816        for fact_instance in facts_to_insert {
817            // Check if template exists for this fact type
818            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
819                // Use template validation
820                self.insert_with_template(&fact_instance.fact_type, fact_instance.data)?
821            } else {
822                // Insert without template validation
823                self.insert(fact_instance.fact_type, fact_instance.data)
824            };
825
826            handles.push(handle);
827        }
828
829        Ok(handles)
830    }
831
832    /// Reset engine and reload all deffacts (similar to CLIPS reset)
833    /// Clears working memory and agenda, then loads all deffacts
834    pub fn reset_with_deffacts(&mut self) -> Vec<FactHandle> {
835        // Clear working memory and agenda
836        self.working_memory = WorkingMemory::new();
837        self.agenda.clear();
838        self.rule_matched_facts.clear();
839
840        // Reload all deffacts
841        self.load_deffacts()
842    }
843
844    /// Insert a typed fact with template validation
845    pub fn insert_with_template(
846        &mut self,
847        template_name: &str,
848        data: TypedFacts,
849    ) -> crate::errors::Result<FactHandle> {
850        // Validate against template
851        self.templates.validate(template_name, &data)?;
852
853        // Insert into working memory
854        Ok(self.insert(template_name.to_string(), data))
855    }
856}
857
858impl Default for IncrementalEngine {
859    fn default() -> Self {
860        Self::new()
861    }
862}
863
864/// Engine statistics
865#[derive(Debug)]
866pub struct IncrementalEngineStats {
867    pub rules: usize,
868    pub working_memory: super::working_memory::WorkingMemoryStats,
869    pub agenda: super::agenda::AgendaStats,
870    pub dependencies: usize,
871}
872
873impl std::fmt::Display for IncrementalEngineStats {
874    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
875        write!(
876            f,
877            "Engine Stats: {} rules, {} fact types tracked\nWM: {}\nAgenda: {}",
878            self.rules, self.dependencies, self.working_memory, self.agenda
879        )
880    }
881}
882
883#[cfg(test)]
884mod tests {
885    use super::*;
886    use crate::rete::alpha::AlphaNode;
887    use crate::rete::network::ReteUlNode;
888
889    #[test]
890    fn test_dependency_graph() {
891        let mut graph = RuleDependencyGraph::new();
892
893        graph.add_dependency(0, "Person".to_string());
894        graph.add_dependency(1, "Person".to_string());
895        graph.add_dependency(1, "Order".to_string());
896
897        let affected = graph.get_affected_rules("Person");
898        assert_eq!(affected.len(), 2);
899        assert!(affected.contains(&0));
900        assert!(affected.contains(&1));
901
902        let deps = graph.get_rule_dependencies(1);
903        assert_eq!(deps.len(), 2);
904        assert!(deps.contains("Person"));
905        assert!(deps.contains("Order"));
906    }
907
908    #[test]
909    fn test_incremental_propagation() {
910        let mut engine = IncrementalEngine::new();
911
912        // Add rule that depends on "Person" type
913        let node = ReteUlNode::UlAlpha(AlphaNode {
914            field: "Person.age".to_string(),
915            operator: ">".to_string(),
916            value: "18".to_string(),
917        });
918
919        let rule = TypedReteUlRule {
920            name: "IsAdult".to_string(),
921            node,
922            priority: 0,
923            no_loop: true,
924            action: std::sync::Arc::new(|_, _| {}),
925        };
926
927        engine.add_rule(rule, vec!["Person".to_string()]);
928
929        // Insert Person fact
930        let mut person = TypedFacts::new();
931        person.set("age", 25i64);
932        let handle = engine.insert("Person".to_string(), person);
933
934        // Check that rule was activated
935        let stats = engine.stats();
936        assert!(stats.agenda.total_activations > 0);
937
938        // Update person
939        let mut updated = TypedFacts::new();
940        updated.set("age", 15i64); // Now under 18
941        engine.update(handle, updated).unwrap();
942
943        // Rule should be re-evaluated (incrementally)
944    }
945}