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_string() == b.as_string(),
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_string() == 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 =
390                    super::network::evaluate_rete_ul_node_typed(&rule.node, &single_fact_data);
391
392                if matches {
393                    // Create activation for this specific fact match
394                    let activation = Activation::new(rule.name.clone(), rule.priority)
395                        .with_no_loop(rule.no_loop)
396                        .with_matched_fact(fact.handle);
397
398                    self.agenda.add_activation(activation);
399                }
400            }
401        }
402    }
403
404    /// Propagate changes for all fact types (re-evaluate all rules)
405    fn propagate_changes(&mut self) {
406        // Get all fact types
407        let fact_types: Vec<String> = self
408            .working_memory
409            .get_all_facts()
410            .iter()
411            .map(|f| f.fact_type.clone())
412            .collect::<std::collections::HashSet<_>>()
413            .into_iter()
414            .collect();
415
416        // Evaluate each fact type using per-fact evaluation
417        for fact_type in fact_types {
418            let facts_of_type = self.working_memory.get_by_type(&fact_type);
419
420            for rule in self.rules.iter() {
421                // Skip if rule has no-loop and already fired
422                if rule.no_loop && self.agenda.has_fired(&rule.name) {
423                    continue;
424                }
425
426                // Check each fact against the rule
427                for fact in &facts_of_type {
428                    let mut single_fact_data = TypedFacts::new();
429                    for (key, value) in fact.data.get_all() {
430                        single_fact_data.set(format!("{}.{}", fact_type, key), value.clone());
431                    }
432
433                    let matches =
434                        super::network::evaluate_rete_ul_node_typed(&rule.node, &single_fact_data);
435
436                    if matches {
437                        let activation = Activation::new(rule.name.clone(), rule.priority)
438                            .with_no_loop(rule.no_loop)
439                            .with_matched_fact(fact.handle);
440
441                        self.agenda.add_activation(activation);
442                    }
443                }
444            }
445        }
446    }
447
448    /// Fire all pending activations
449    pub fn fire_all(&mut self) -> Vec<String> {
450        let mut fired_rules = Vec::new();
451        let max_iterations = 1000; // Prevent infinite loops
452        let mut iteration_count = 0;
453
454        while let Some(activation) = self.agenda.get_next_activation() {
455            iteration_count += 1;
456            if iteration_count > max_iterations {
457                eprintln!("WARNING: Maximum iterations ({}) reached in fire_all(). Possible infinite loop!", max_iterations);
458                break;
459            }
460
461            // Find rule
462            if let Some((_idx, rule)) = self
463                .rules
464                .iter_mut()
465                .enumerate()
466                .find(|(_, r)| r.name == activation.rule_name)
467            {
468                // Validate that matched fact still exists (hasn't been retracted)
469                if let Some(matched_handle) = activation.matched_fact_handle {
470                    if self.working_memory.get(&matched_handle).is_none() {
471                        // Fact was retracted, skip this activation
472                        continue;
473                    }
474                }
475
476                // Execute action on a copy of all facts
477                let original_facts = self.working_memory.to_typed_facts();
478                let mut modified_facts = original_facts.clone();
479
480                // Inject matched fact handle if available
481                if let Some(matched_handle) = activation.matched_fact_handle {
482                    // Find the fact type from working memory
483                    if let Some(fact) = self.working_memory.get(&matched_handle) {
484                        modified_facts.set_fact_handle(fact.fact_type.clone(), matched_handle);
485                    }
486                }
487
488                let mut action_results = super::ActionResults::new();
489                (rule.action)(&mut modified_facts, &mut action_results);
490
491                // Update working memory: detect changes and apply them
492                // Build a map of fact_type -> updated fields
493                let mut updates_by_type: HashMap<String, Vec<(String, FactValue)>> = HashMap::new();
494
495                for (key, value) in modified_facts.get_all() {
496                    // Keys are in format "FactType.field" or "FactType.handle.field"
497                    // We want to extract the FactType and field name
498                    if let Some(original_value) = original_facts.get(key) {
499                        if original_value != value {
500                            // Value changed! Extract fact type and field
501                            let parts: Vec<&str> = key.split('.').collect();
502                            if parts.len() >= 2 {
503                                let fact_type = parts[0].to_string();
504                                // Field is the last part (skip handle if present)
505                                let field = if parts.len() == 2 {
506                                    parts[1].to_string()
507                                } else {
508                                    parts[parts.len() - 1].to_string()
509                                };
510
511                                updates_by_type
512                                    .entry(fact_type)
513                                    .or_default()
514                                    .push((field, value.clone()));
515                            }
516                        }
517                    } else {
518                        // New field added
519                        let parts: Vec<&str> = key.split('.').collect();
520                        if parts.len() >= 2 {
521                            let fact_type = parts[0].to_string();
522                            let field = if parts.len() == 2 {
523                                parts[1].to_string()
524                            } else {
525                                parts[parts.len() - 1].to_string()
526                            };
527
528                            updates_by_type
529                                .entry(fact_type)
530                                .or_default()
531                                .push((field, value.clone()));
532                        }
533                    }
534                }
535
536                // Apply updates to working memory facts
537                for (fact_type, field_updates) in updates_by_type {
538                    // Get handles of all facts of this type (collect to avoid borrow issues)
539                    let fact_handles: Vec<FactHandle> = self
540                        .working_memory
541                        .get_by_type(&fact_type)
542                        .iter()
543                        .map(|f| f.handle)
544                        .collect();
545
546                    for handle in fact_handles {
547                        if let Some(fact) = self.working_memory.get(&handle) {
548                            let mut updated_data = fact.data.clone();
549
550                            // Apply all field updates
551                            for (field, value) in &field_updates {
552                                updated_data.set(field, value.clone());
553                            }
554
555                            let _ = self.working_memory.update(handle, updated_data);
556                        }
557                    }
558                }
559
560                // Re-evaluate matches after working memory update
561                // This allows subsequent rules to see the updated values
562                self.propagate_changes();
563
564                // Process action results (retractions, agenda activations, etc.)
565                self.process_action_results(action_results);
566
567                // Track fired rule
568                fired_rules.push(activation.rule_name.clone());
569                self.agenda.mark_rule_fired(&activation);
570            }
571        }
572
573        fired_rules
574    }
575
576    /// Process action results from rule execution
577    fn process_action_results(&mut self, results: super::ActionResults) {
578        for result in results.results {
579            match result {
580                super::ActionResult::Retract(handle) => {
581                    // Retract fact by handle
582                    if let Err(e) = self.retract(handle) {
583                        eprintln!("❌ Failed to retract fact {:?}: {}", handle, e);
584                    }
585                }
586                super::ActionResult::RetractByType(fact_type) => {
587                    // Retract first fact of this type
588                    let facts_of_type = self.working_memory.get_by_type(&fact_type);
589                    if let Some(fact) = facts_of_type.first() {
590                        let handle = fact.handle;
591                        if let Err(e) = self.retract(handle) {
592                            eprintln!("❌ Failed to retract fact {:?}: {}", handle, e);
593                        }
594                    }
595                }
596                super::ActionResult::Update(handle) => {
597                    // Re-evaluate rules that depend on this fact type
598                    if let Some(fact) = self.working_memory.get(&handle) {
599                        let fact_type = fact.fact_type.clone();
600                        self.propagate_changes_for_type(&fact_type);
601                    }
602                }
603                super::ActionResult::ActivateAgendaGroup(group) => {
604                    // Activate agenda group
605                    self.agenda.set_focus(group);
606                }
607                super::ActionResult::InsertFact { fact_type, data } => {
608                    // Insert new explicit fact
609                    self.insert_explicit(fact_type, data);
610                }
611                super::ActionResult::InsertLogicalFact {
612                    fact_type,
613                    data,
614                    rule_name,
615                    premises,
616                } => {
617                    // Insert new logical fact
618                    let _handle = self.insert_logical(fact_type, data, rule_name, premises);
619                }
620                super::ActionResult::CallFunction {
621                    function_name,
622                    args,
623                } => {
624                    // Try to execute function if registered
625                    if let Some(func) = self.custom_functions.get(&function_name) {
626                        // Convert string args to FactValues
627                        let fact_values: Vec<FactValue> =
628                            args.iter().map(|s| FactValue::String(s.clone())).collect();
629
630                        // Execute function (ignore return value for actions)
631                        let all_facts = self.working_memory.to_typed_facts();
632                        match func(&fact_values, &all_facts) {
633                            Ok(_) => println!("✅ Called function: {}", function_name),
634                            Err(e) => eprintln!("❌ Function {} failed: {}", function_name, e),
635                        }
636                    } else {
637                        // Function not registered, just log
638                        println!("🔧 Function call queued: {}({:?})", function_name, args);
639                    }
640                }
641                super::ActionResult::ScheduleRule {
642                    rule_name,
643                    delay_ms,
644                } => {
645                    // Log scheduled rules (requires scheduler to actually execute)
646                    println!("⏰ Rule scheduled: {} after {}ms", rule_name, delay_ms);
647                    // TODO: Implement rule scheduler
648                }
649                super::ActionResult::None => {
650                    // No action needed
651                }
652            }
653        }
654    }
655
656    /// Get working memory
657    pub fn working_memory(&self) -> &WorkingMemory {
658        &self.working_memory
659    }
660
661    /// Get mutable working memory
662    pub fn working_memory_mut(&mut self) -> &mut WorkingMemory {
663        &mut self.working_memory
664    }
665
666    /// Get agenda
667    pub fn agenda(&self) -> &AdvancedAgenda {
668        &self.agenda
669    }
670
671    /// Get mutable agenda
672    pub fn agenda_mut(&mut self) -> &mut AdvancedAgenda {
673        &mut self.agenda
674    }
675
676    /// Set conflict resolution strategy
677    ///
678    /// Controls how conflicting rules in the agenda are ordered.
679    /// Available strategies: Salience (default), LEX, MEA, Depth, Breadth, Simplicity, Complexity, Random
680    pub fn set_conflict_resolution_strategy(
681        &mut self,
682        strategy: super::agenda::ConflictResolutionStrategy,
683    ) {
684        self.agenda.set_strategy(strategy);
685    }
686
687    /// Get current conflict resolution strategy
688    pub fn conflict_resolution_strategy(&self) -> super::agenda::ConflictResolutionStrategy {
689        self.agenda.strategy()
690    }
691
692    /// Get statistics
693    pub fn stats(&self) -> IncrementalEngineStats {
694        IncrementalEngineStats {
695            rules: self.rules.len(),
696            working_memory: self.working_memory.stats(),
697            agenda: self.agenda.stats(),
698            dependencies: self.dependencies.fact_type_to_rules.len(),
699        }
700    }
701
702    /// Clear fired flags and reset agenda
703    pub fn reset(&mut self) {
704        self.agenda.reset_fired_flags();
705    }
706
707    /// Get template registry
708    pub fn templates(&self) -> &TemplateRegistry {
709        &self.templates
710    }
711
712    /// Get mutable template registry
713    pub fn templates_mut(&mut self) -> &mut TemplateRegistry {
714        &mut self.templates
715    }
716
717    /// Register a custom function for Test CE support
718    ///
719    /// # Example
720    /// ```
721    /// use rust_rule_engine::rete::{IncrementalEngine, FactValue};
722    ///
723    /// let mut engine = IncrementalEngine::new();
724    /// engine.register_function(
725    ///     "is_valid_email",
726    ///     |args, _facts| {
727    ///         if let Some(FactValue::String(email)) = args.first() {
728    ///             Ok(FactValue::Boolean(email.contains('@')))
729    ///         } else {
730    ///             Ok(FactValue::Boolean(false))
731    ///         }
732    ///     }
733    /// );
734    /// ```
735    pub fn register_function<F>(&mut self, name: &str, func: F)
736    where
737        F: Fn(&[FactValue], &TypedFacts) -> Result<FactValue> + Send + Sync + 'static,
738    {
739        self.custom_functions
740            .insert(name.to_string(), Arc::new(func));
741    }
742
743    /// Get a custom function by name (for Test CE evaluation)
744    pub fn get_function(&self, name: &str) -> Option<&ReteCustomFunction> {
745        self.custom_functions.get(name)
746    }
747
748    /// Get global variables registry
749    pub fn globals(&self) -> &GlobalsRegistry {
750        &self.globals
751    }
752
753    /// Get mutable global variables registry
754    pub fn globals_mut(&mut self) -> &mut GlobalsRegistry {
755        &mut self.globals
756    }
757
758    /// Get deffacts registry
759    pub fn deffacts(&self) -> &DeffactsRegistry {
760        &self.deffacts
761    }
762
763    /// Get mutable deffacts registry
764    pub fn deffacts_mut(&mut self) -> &mut DeffactsRegistry {
765        &mut self.deffacts
766    }
767
768    /// Load all registered deffacts into working memory
769    /// Returns handles of all inserted facts
770    pub fn load_deffacts(&mut self) -> Vec<FactHandle> {
771        let mut handles = Vec::new();
772
773        // Get all facts from all registered deffacts
774        let all_facts = self.deffacts.get_all_facts();
775
776        for (_deffacts_name, fact_instance) in all_facts {
777            // Check if template exists for this fact type
778            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
779                // Use template validation
780                match self.insert_with_template(&fact_instance.fact_type, fact_instance.data) {
781                    Ok(h) => h,
782                    Err(_) => continue, // Skip invalid facts
783                }
784            } else {
785                // Insert without template validation
786                self.insert(fact_instance.fact_type, fact_instance.data)
787            };
788
789            handles.push(handle);
790        }
791
792        handles
793    }
794
795    /// Load a specific deffacts set by name
796    /// Returns handles of inserted facts or error if deffacts not found
797    pub fn load_deffacts_by_name(&mut self, name: &str) -> crate::errors::Result<Vec<FactHandle>> {
798        // Clone the facts to avoid borrow checker issues
799        let facts_to_insert = {
800            let deffacts = self.deffacts.get(name).ok_or_else(|| {
801                crate::errors::RuleEngineError::EvaluationError {
802                    message: format!("Deffacts '{}' not found", name),
803                }
804            })?;
805            deffacts.facts.clone()
806        };
807
808        let mut handles = Vec::new();
809
810        for fact_instance in facts_to_insert {
811            // Check if template exists for this fact type
812            let handle = if self.templates.get(&fact_instance.fact_type).is_some() {
813                // Use template validation
814                self.insert_with_template(&fact_instance.fact_type, fact_instance.data)?
815            } else {
816                // Insert without template validation
817                self.insert(fact_instance.fact_type, fact_instance.data)
818            };
819
820            handles.push(handle);
821        }
822
823        Ok(handles)
824    }
825
826    /// Reset engine and reload all deffacts (similar to CLIPS reset)
827    /// Clears working memory and agenda, then loads all deffacts
828    pub fn reset_with_deffacts(&mut self) -> Vec<FactHandle> {
829        // Clear working memory and agenda
830        self.working_memory = WorkingMemory::new();
831        self.agenda.clear();
832        self.rule_matched_facts.clear();
833
834        // Reload all deffacts
835        self.load_deffacts()
836    }
837
838    /// Insert a typed fact with template validation
839    pub fn insert_with_template(
840        &mut self,
841        template_name: &str,
842        data: TypedFacts,
843    ) -> crate::errors::Result<FactHandle> {
844        // Validate against template
845        self.templates.validate(template_name, &data)?;
846
847        // Insert into working memory
848        Ok(self.insert(template_name.to_string(), data))
849    }
850}
851
852impl Default for IncrementalEngine {
853    fn default() -> Self {
854        Self::new()
855    }
856}
857
858/// Engine statistics
859#[derive(Debug)]
860pub struct IncrementalEngineStats {
861    pub rules: usize,
862    pub working_memory: super::working_memory::WorkingMemoryStats,
863    pub agenda: super::agenda::AgendaStats,
864    pub dependencies: usize,
865}
866
867impl std::fmt::Display for IncrementalEngineStats {
868    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
869        write!(
870            f,
871            "Engine Stats: {} rules, {} fact types tracked\nWM: {}\nAgenda: {}",
872            self.rules, self.dependencies, self.working_memory, self.agenda
873        )
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880    use crate::rete::alpha::AlphaNode;
881    use crate::rete::network::ReteUlNode;
882
883    #[test]
884    fn test_dependency_graph() {
885        let mut graph = RuleDependencyGraph::new();
886
887        graph.add_dependency(0, "Person".to_string());
888        graph.add_dependency(1, "Person".to_string());
889        graph.add_dependency(1, "Order".to_string());
890
891        let affected = graph.get_affected_rules("Person");
892        assert_eq!(affected.len(), 2);
893        assert!(affected.contains(&0));
894        assert!(affected.contains(&1));
895
896        let deps = graph.get_rule_dependencies(1);
897        assert_eq!(deps.len(), 2);
898        assert!(deps.contains("Person"));
899        assert!(deps.contains("Order"));
900    }
901
902    #[test]
903    fn test_incremental_propagation() {
904        let mut engine = IncrementalEngine::new();
905
906        // Add rule that depends on "Person" type
907        let node = ReteUlNode::UlAlpha(AlphaNode {
908            field: "Person.age".to_string(),
909            operator: ">".to_string(),
910            value: "18".to_string(),
911        });
912
913        let rule = TypedReteUlRule {
914            name: "IsAdult".to_string(),
915            node,
916            priority: 0,
917            no_loop: true,
918            action: std::sync::Arc::new(|_, _| {}),
919        };
920
921        engine.add_rule(rule, vec!["Person".to_string()]);
922
923        // Insert Person fact
924        let mut person = TypedFacts::new();
925        person.set("age", 25i64);
926        let handle = engine.insert("Person".to_string(), person);
927
928        // Check that rule was activated
929        let stats = engine.stats();
930        assert!(stats.agenda.total_activations > 0);
931
932        // Update person
933        let mut updated = TypedFacts::new();
934        updated.set("age", 15i64); // Now under 18
935        engine.update(handle, updated).unwrap();
936
937        // Rule should be re-evaluated (incrementally)
938    }
939}