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