rust_rule_engine/engine/
engine.rs

1use crate::engine::{analytics::RuleAnalytics, facts::Facts, knowledge_base::KnowledgeBase};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Value};
4use std::collections::HashMap;
5use std::time::{Duration, Instant};
6
7/// Type for custom function implementations
8pub type CustomFunction = Box<dyn Fn(&[Value], &Facts) -> Result<Value> + Send + Sync>;
9
10/// Configuration options for the rule engine
11#[derive(Debug, Clone)]
12pub struct EngineConfig {
13    /// Maximum number of execution cycles
14    pub max_cycles: usize,
15    /// Execution timeout
16    pub timeout: Option<Duration>,
17    /// Enable performance statistics collection
18    pub enable_stats: bool,
19    /// Enable debug mode with verbose logging
20    pub debug_mode: bool,
21}
22
23impl Default for EngineConfig {
24    fn default() -> Self {
25        Self {
26            max_cycles: 100,
27            timeout: Some(Duration::from_secs(30)),
28            enable_stats: true,
29            debug_mode: false,
30        }
31    }
32}
33
34/// Result of rule engine execution
35#[derive(Debug, Clone)]
36pub struct GruleExecutionResult {
37    /// Number of execution cycles
38    pub cycle_count: usize,
39    /// Number of rules evaluated
40    pub rules_evaluated: usize,
41    /// Number of rules that fired
42    pub rules_fired: usize,
43    /// Total execution time
44    pub execution_time: Duration,
45}
46
47/// Rust Rule Engine - High-performance rule execution engine
48pub struct RustRuleEngine {
49    knowledge_base: KnowledgeBase,
50    config: EngineConfig,
51    custom_functions: HashMap<String, CustomFunction>,
52    analytics: Option<RuleAnalytics>,
53}
54
55impl RustRuleEngine {
56    /// Create a new RustRuleEngine with default configuration
57    pub fn new(knowledge_base: KnowledgeBase) -> Self {
58        Self {
59            knowledge_base,
60            config: EngineConfig::default(),
61            custom_functions: HashMap::new(),
62            analytics: None,
63        }
64    }
65
66    /// Create a new RustRuleEngine with custom configuration
67    pub fn with_config(knowledge_base: KnowledgeBase, config: EngineConfig) -> Self {
68        Self {
69            knowledge_base,
70            config,
71            custom_functions: HashMap::new(),
72            analytics: None,
73        }
74    }
75
76    /// Register a custom function
77    pub fn register_function<F>(&mut self, name: &str, func: F)
78    where
79        F: Fn(&[Value], &Facts) -> Result<Value> + Send + Sync + 'static,
80    {
81        self.custom_functions
82            .insert(name.to_string(), Box::new(func));
83    }
84
85    /// Enable analytics with custom configuration
86    pub fn enable_analytics(&mut self, analytics: RuleAnalytics) {
87        self.analytics = Some(analytics);
88    }
89
90    /// Disable analytics
91    pub fn disable_analytics(&mut self) {
92        self.analytics = None;
93    }
94
95    /// Get reference to analytics data
96    pub fn analytics(&self) -> Option<&RuleAnalytics> {
97        self.analytics.as_ref()
98    }
99
100    /// Check if a custom function is registered
101    pub fn has_function(&self, name: &str) -> bool {
102        self.custom_functions.contains_key(name)
103    }
104
105    /// Get reference to the knowledge base
106    pub fn knowledge_base(&self) -> &KnowledgeBase {
107        &self.knowledge_base
108    }
109
110    /// Get mutable reference to the knowledge base
111    pub fn knowledge_base_mut(&mut self) -> &mut KnowledgeBase {
112        &mut self.knowledge_base
113    }
114
115    /// Execute all rules in the knowledge base against the given facts
116    pub fn execute(&mut self, facts: &Facts) -> Result<GruleExecutionResult> {
117        let start_time = Instant::now();
118        let mut cycle_count = 0;
119        let mut rules_evaluated = 0;
120        let mut rules_fired = 0;
121
122        if self.config.debug_mode {
123            println!(
124                "🚀 Starting rule execution with {} rules",
125                self.knowledge_base.get_rules().len()
126            );
127        }
128
129        for cycle in 0..self.config.max_cycles {
130            cycle_count = cycle + 1;
131            let mut any_rule_fired = false;
132            let mut fired_rules_in_cycle = std::collections::HashSet::new();
133
134            // Check for timeout
135            if let Some(timeout) = self.config.timeout {
136                if start_time.elapsed() > timeout {
137                    return Err(RuleEngineError::EvaluationError {
138                        message: "Execution timeout exceeded".to_string(),
139                    });
140                }
141            }
142
143            // Get rules sorted by salience (highest first)
144            let mut rules = self.knowledge_base.get_rules().clone();
145            rules.sort_by(|a, b| b.salience.cmp(&a.salience));
146
147            for rule in &rules {
148                if !rule.enabled {
149                    continue;
150                }
151
152                // Check no-loop: if rule has no_loop=true and already fired this cycle, skip
153                if rule.no_loop && fired_rules_in_cycle.contains(&rule.name) {
154                    continue;
155                }
156
157                rules_evaluated += 1;
158                let rule_start = Instant::now();
159
160                if self.config.debug_mode {
161                    println!("📝 Evaluating rule: {}", rule.name);
162                }
163
164                // Evaluate rule conditions
165                if self.evaluate_conditions(&rule.conditions, facts)? {
166                    if self.config.debug_mode {
167                        println!(
168                            "🔥 Rule '{}' fired (salience: {})",
169                            rule.name, rule.salience
170                        );
171                    }
172
173                    // Execute actions
174                    for action in &rule.actions {
175                        self.execute_action(action, facts)?;
176                    }
177
178                    let rule_duration = rule_start.elapsed();
179
180                    // Record analytics if enabled
181                    if let Some(analytics) = &mut self.analytics {
182                        analytics.record_execution(&rule.name, rule_duration, true, true, None, 0);
183                    }
184
185                    rules_fired += 1;
186                    any_rule_fired = true;
187
188                    // Track that this rule fired in this cycle (for no-loop support)
189                    fired_rules_in_cycle.insert(rule.name.clone());
190                } else {
191                    let rule_duration = rule_start.elapsed();
192
193                    // Record analytics for failed rules too
194                    if let Some(analytics) = &mut self.analytics {
195                        analytics.record_execution(
196                            &rule.name,
197                            rule_duration,
198                            false,
199                            false,
200                            None,
201                            0,
202                        );
203                    }
204                }
205            }
206
207            // If no rules fired in this cycle, we're done
208            if !any_rule_fired {
209                break;
210            }
211        }
212
213        let execution_time = start_time.elapsed();
214
215        Ok(GruleExecutionResult {
216            cycle_count,
217            rules_evaluated,
218            rules_fired,
219            execution_time,
220        })
221    }
222
223    /// Evaluate conditions against facts
224    fn evaluate_conditions(
225        &self,
226        conditions: &crate::engine::rule::ConditionGroup,
227        facts: &Facts,
228    ) -> Result<bool> {
229        use crate::engine::rule::ConditionGroup;
230
231        match conditions {
232            ConditionGroup::Single(condition) => self.evaluate_single_condition(condition, facts),
233            ConditionGroup::Compound {
234                left,
235                operator,
236                right,
237            } => {
238                let left_result = self.evaluate_conditions(left, facts)?;
239                let right_result = self.evaluate_conditions(right, facts)?;
240
241                match operator {
242                    crate::types::LogicalOperator::And => Ok(left_result && right_result),
243                    crate::types::LogicalOperator::Or => Ok(left_result || right_result),
244                    crate::types::LogicalOperator::Not => Err(RuleEngineError::EvaluationError {
245                        message: "NOT operator should not appear in compound conditions"
246                            .to_string(),
247                    }),
248                }
249            }
250            ConditionGroup::Not(condition) => {
251                let result = self.evaluate_conditions(condition, facts)?;
252                Ok(!result)
253            }
254        }
255    }
256
257    /// Evaluate a single condition
258    fn evaluate_single_condition(
259        &self,
260        condition: &crate::engine::rule::Condition,
261        facts: &Facts,
262    ) -> Result<bool> {
263        // Get the field value from facts
264        let field_value = facts.get_nested(&condition.field);
265
266        if field_value.is_none() {
267            return Ok(false); // Field not found, condition fails
268        }
269
270        let field_value = field_value.unwrap();
271
272        // Compare using the operator
273        Ok(condition.operator.evaluate(&field_value, &condition.value))
274    }
275
276    /// Execute an action
277    fn execute_action(&self, action: &ActionType, facts: &Facts) -> Result<()> {
278        match action {
279            ActionType::Set { field, value } => {
280                facts.set_nested(field, value.clone())?;
281                if self.config.debug_mode {
282                    println!("  ✅ Set {field} = {value:?}");
283                }
284            }
285            ActionType::Log { message } => {
286                println!("📋 LOG: {}", message);
287            }
288            ActionType::Call { function, args } => {
289                let result = self.execute_function_call(function, args, facts)?;
290                if self.config.debug_mode {
291                    println!("  📞 Called {function}({args:?}) -> {result}");
292                }
293            }
294            ActionType::MethodCall {
295                object,
296                method,
297                args,
298            } => {
299                let result = self.execute_method_call(object, method, args, facts)?;
300                if self.config.debug_mode {
301                    println!("  🔧 Called {object}.{method}({args:?}) -> {result}");
302                }
303            }
304            ActionType::Update { object } => {
305                if self.config.debug_mode {
306                    println!("  🔄 Updated {object}");
307                }
308                // Update action is mainly for working memory management
309                // In this implementation, it's mostly a no-op since we update in place
310            }
311            ActionType::Custom {
312                action_type,
313                params,
314            } => {
315                if self.config.debug_mode {
316                    println!("  🎯 Custom action: {action_type} with params: {params:?}");
317                }
318            }
319        }
320        Ok(())
321    }
322
323    /// Execute function call
324    fn execute_function_call(
325        &self,
326        function: &str,
327        args: &[Value],
328        facts: &Facts,
329    ) -> Result<String> {
330        let function_lower = function.to_lowercase();
331
332        // Handle built-in utility functions
333        match function_lower.as_str() {
334            "log" | "print" | "println" => self.handle_log_function(args),
335            "update" | "refresh" => self.handle_update_function(args),
336            "now" | "timestamp" => self.handle_timestamp_function(),
337            "random" => self.handle_random_function(args),
338            "format" | "sprintf" => self.handle_format_function(args),
339            "length" | "size" | "count" => self.handle_length_function(args),
340            "sum" | "add" => self.handle_sum_function(args),
341            "max" | "maximum" => self.handle_max_function(args),
342            "min" | "minimum" => self.handle_min_function(args),
343            "avg" | "average" => self.handle_average_function(args),
344            "round" => self.handle_round_function(args),
345            "floor" => self.handle_floor_function(args),
346            "ceil" | "ceiling" => self.handle_ceil_function(args),
347            "abs" | "absolute" => self.handle_abs_function(args),
348            "contains" | "includes" => self.handle_contains_function(args),
349            "startswith" | "begins_with" => self.handle_starts_with_function(args),
350            "endswith" | "ends_with" => self.handle_ends_with_function(args),
351            "lowercase" | "tolower" => self.handle_lowercase_function(args),
352            "uppercase" | "toupper" => self.handle_uppercase_function(args),
353            "trim" | "strip" => self.handle_trim_function(args),
354            "split" => self.handle_split_function(args),
355            "join" => self.handle_join_function(args),
356            _ => {
357                // Try to call custom user-defined function
358                self.handle_custom_function(function, args, facts)
359            }
360        }
361    }
362
363    /// Handle logging functions (log, print, println)
364    fn handle_log_function(&self, args: &[Value]) -> Result<String> {
365        let message = if args.is_empty() {
366            "".to_string()
367        } else if args.len() == 1 {
368            args[0].to_string()
369        } else {
370            args.iter()
371                .map(|v| v.to_string())
372                .collect::<Vec<_>>()
373                .join(" ")
374        };
375
376        println!("📋 {}", message);
377        Ok(message)
378    }
379
380    /// Handle update/refresh functions
381    fn handle_update_function(&self, args: &[Value]) -> Result<String> {
382        if let Some(arg) = args.first() {
383            Ok(format!("Updated: {}", arg.to_string()))
384        } else {
385            Ok("Updated".to_string())
386        }
387    }
388
389    /// Handle timestamp function
390    fn handle_timestamp_function(&self) -> Result<String> {
391        use std::time::{SystemTime, UNIX_EPOCH};
392        let timestamp = SystemTime::now()
393            .duration_since(UNIX_EPOCH)
394            .map_err(|e| RuleEngineError::EvaluationError {
395                message: format!("Failed to get timestamp: {}", e),
396            })?
397            .as_secs();
398        Ok(timestamp.to_string())
399    }
400
401    /// Handle random function
402    fn handle_random_function(&self, args: &[Value]) -> Result<String> {
403        use std::collections::hash_map::DefaultHasher;
404        use std::hash::{Hash, Hasher};
405
406        // Simple pseudo-random based on current time (for deterministic behavior in tests)
407        let mut hasher = DefaultHasher::new();
408        std::time::SystemTime::now().hash(&mut hasher);
409        let random_value = hasher.finish();
410
411        if args.is_empty() {
412            Ok((random_value % 100).to_string()) // 0-99
413        } else if let Some(Value::Number(max)) = args.first() {
414            let max_val = *max as u64;
415            Ok((random_value % max_val).to_string())
416        } else {
417            Ok(random_value.to_string())
418        }
419    }
420
421    /// Handle format function (simple sprintf-like)
422    fn handle_format_function(&self, args: &[Value]) -> Result<String> {
423        if args.is_empty() {
424            return Ok("".to_string());
425        }
426
427        let template = args[0].to_string();
428        let values: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
429
430        // Simple placeholder replacement: {0}, {1}, etc.
431        let mut result = template;
432        for (i, value) in values.iter().enumerate() {
433            result = result.replace(&format!("{{{}}}", i), value);
434        }
435
436        Ok(result)
437    }
438
439    /// Handle length/size functions
440    fn handle_length_function(&self, args: &[Value]) -> Result<String> {
441        if let Some(arg) = args.first() {
442            match arg {
443                Value::String(s) => Ok(s.len().to_string()),
444                Value::Array(arr) => Ok(arr.len().to_string()),
445                Value::Object(obj) => Ok(obj.len().to_string()),
446                _ => Ok("1".to_string()), // Single value has length 1
447            }
448        } else {
449            Ok("0".to_string())
450        }
451    }
452
453    /// Handle sum function
454    fn handle_sum_function(&self, args: &[Value]) -> Result<String> {
455        let sum = args.iter().fold(0.0, |acc, val| match val {
456            Value::Number(n) => acc + n,
457            Value::Integer(i) => acc + (*i as f64),
458            _ => acc,
459        });
460        Ok(sum.to_string())
461    }
462
463    /// Handle max function
464    fn handle_max_function(&self, args: &[Value]) -> Result<String> {
465        let max = args.iter().fold(f64::NEG_INFINITY, |acc, val| match val {
466            Value::Number(n) => acc.max(*n),
467            Value::Integer(i) => acc.max(*i as f64),
468            _ => acc,
469        });
470        Ok(max.to_string())
471    }
472
473    /// Handle min function
474    fn handle_min_function(&self, args: &[Value]) -> Result<String> {
475        let min = args.iter().fold(f64::INFINITY, |acc, val| match val {
476            Value::Number(n) => acc.min(*n),
477            Value::Integer(i) => acc.min(*i as f64),
478            _ => acc,
479        });
480        Ok(min.to_string())
481    }
482
483    /// Handle average function
484    fn handle_average_function(&self, args: &[Value]) -> Result<String> {
485        if args.is_empty() {
486            return Ok("0".to_string());
487        }
488
489        let (sum, count) = args.iter().fold((0.0, 0), |(sum, count), val| match val {
490            Value::Number(n) => (sum + n, count + 1),
491            Value::Integer(i) => (sum + (*i as f64), count + 1),
492            _ => (sum, count),
493        });
494
495        if count > 0 {
496            Ok((sum / count as f64).to_string())
497        } else {
498            Ok("0".to_string())
499        }
500    }
501
502    /// Handle mathematical functions
503    fn handle_round_function(&self, args: &[Value]) -> Result<String> {
504        if let Some(Value::Number(n)) = args.first() {
505            Ok(n.round().to_string())
506        } else if let Some(Value::Integer(i)) = args.first() {
507            Ok(i.to_string())
508        } else {
509            Err(RuleEngineError::EvaluationError {
510                message: "round() requires a numeric argument".to_string(),
511            })
512        }
513    }
514
515    fn handle_floor_function(&self, args: &[Value]) -> Result<String> {
516        if let Some(Value::Number(n)) = args.first() {
517            Ok(n.floor().to_string())
518        } else if let Some(Value::Integer(i)) = args.first() {
519            Ok(i.to_string())
520        } else {
521            Err(RuleEngineError::EvaluationError {
522                message: "floor() requires a numeric argument".to_string(),
523            })
524        }
525    }
526
527    fn handle_ceil_function(&self, args: &[Value]) -> Result<String> {
528        if let Some(Value::Number(n)) = args.first() {
529            Ok(n.ceil().to_string())
530        } else if let Some(Value::Integer(i)) = args.first() {
531            Ok(i.to_string())
532        } else {
533            Err(RuleEngineError::EvaluationError {
534                message: "ceil() requires a numeric argument".to_string(),
535            })
536        }
537    }
538
539    fn handle_abs_function(&self, args: &[Value]) -> Result<String> {
540        if let Some(Value::Number(n)) = args.first() {
541            Ok(n.abs().to_string())
542        } else if let Some(Value::Integer(i)) = args.first() {
543            Ok(i.abs().to_string())
544        } else {
545            Err(RuleEngineError::EvaluationError {
546                message: "abs() requires a numeric argument".to_string(),
547            })
548        }
549    }
550
551    /// Handle string functions
552    fn handle_contains_function(&self, args: &[Value]) -> Result<String> {
553        if args.len() >= 2 {
554            let haystack = args[0].to_string();
555            let needle = args[1].to_string();
556            Ok(haystack.contains(&needle).to_string())
557        } else {
558            Err(RuleEngineError::EvaluationError {
559                message: "contains() requires 2 arguments".to_string(),
560            })
561        }
562    }
563
564    fn handle_starts_with_function(&self, args: &[Value]) -> Result<String> {
565        if args.len() >= 2 {
566            let text = args[0].to_string();
567            let prefix = args[1].to_string();
568            Ok(text.starts_with(&prefix).to_string())
569        } else {
570            Err(RuleEngineError::EvaluationError {
571                message: "startswith() requires 2 arguments".to_string(),
572            })
573        }
574    }
575
576    fn handle_ends_with_function(&self, args: &[Value]) -> Result<String> {
577        if args.len() >= 2 {
578            let text = args[0].to_string();
579            let suffix = args[1].to_string();
580            Ok(text.ends_with(&suffix).to_string())
581        } else {
582            Err(RuleEngineError::EvaluationError {
583                message: "endswith() requires 2 arguments".to_string(),
584            })
585        }
586    }
587
588    fn handle_lowercase_function(&self, args: &[Value]) -> Result<String> {
589        if let Some(arg) = args.first() {
590            Ok(arg.to_string().to_lowercase())
591        } else {
592            Err(RuleEngineError::EvaluationError {
593                message: "lowercase() requires 1 argument".to_string(),
594            })
595        }
596    }
597
598    fn handle_uppercase_function(&self, args: &[Value]) -> Result<String> {
599        if let Some(arg) = args.first() {
600            Ok(arg.to_string().to_uppercase())
601        } else {
602            Err(RuleEngineError::EvaluationError {
603                message: "uppercase() requires 1 argument".to_string(),
604            })
605        }
606    }
607
608    fn handle_trim_function(&self, args: &[Value]) -> Result<String> {
609        if let Some(arg) = args.first() {
610            Ok(arg.to_string().trim().to_string())
611        } else {
612            Err(RuleEngineError::EvaluationError {
613                message: "trim() requires 1 argument".to_string(),
614            })
615        }
616    }
617
618    fn handle_split_function(&self, args: &[Value]) -> Result<String> {
619        if args.len() >= 2 {
620            let text = args[0].to_string();
621            let delimiter = args[1].to_string();
622            let parts: Vec<String> = text.split(&delimiter).map(|s| s.to_string()).collect();
623            Ok(format!("{:?}", parts)) // Return as debug string for now
624        } else {
625            Err(RuleEngineError::EvaluationError {
626                message: "split() requires 2 arguments".to_string(),
627            })
628        }
629    }
630
631    fn handle_join_function(&self, args: &[Value]) -> Result<String> {
632        if args.len() >= 2 {
633            let delimiter = args[0].to_string();
634            let parts: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
635            Ok(parts.join(&delimiter))
636        } else {
637            Err(RuleEngineError::EvaluationError {
638                message: "join() requires at least 2 arguments".to_string(),
639            })
640        }
641    }
642
643    /// Handle custom user-defined functions
644    fn handle_custom_function(
645        &self,
646        function: &str,
647        args: &[Value],
648        facts: &Facts,
649    ) -> Result<String> {
650        // Check if we have a registered custom function
651        if let Some(custom_func) = self.custom_functions.get(function) {
652            if self.config.debug_mode {
653                println!("🎯 Calling registered function: {}({:?})", function, args);
654            }
655
656            match custom_func(args, facts) {
657                Ok(result) => Ok(result.to_string()),
658                Err(e) => Err(e),
659            }
660        } else {
661            // Function not found - return error or placeholder
662            if self.config.debug_mode {
663                println!("⚠️ Custom function '{}' not registered", function);
664            }
665
666            Err(RuleEngineError::EvaluationError {
667                message: format!("Function '{}' is not registered. Use engine.register_function() to add custom functions.", function),
668            })
669        }
670    }
671
672    /// Execute method call on object
673    fn execute_method_call(
674        &self,
675        object_name: &str,
676        method: &str,
677        args: &[Value],
678        facts: &Facts,
679    ) -> Result<String> {
680        // Get the object from facts
681        let Some(object_value) = facts.get(object_name) else {
682            return Err(RuleEngineError::EvaluationError {
683                message: format!("Object '{}' not found in facts", object_name),
684            });
685        };
686
687        let method_lower = method.to_lowercase();
688
689        // Handle setter methods (set + property name)
690        if method_lower.starts_with("set") && args.len() == 1 {
691            return self.handle_setter_method(object_name, method, &args[0], object_value, facts);
692        }
693
694        // Handle getter methods (get + property name)
695        if method_lower.starts_with("get") && args.is_empty() {
696            return self.handle_getter_method(object_name, method, &object_value);
697        }
698
699        // Handle built-in methods
700        match method_lower.as_str() {
701            "tostring" => Ok(object_value.to_string()),
702            "update" => {
703                facts.add_value(object_name, object_value)?;
704                Ok(format!("Updated {}", object_name))
705            }
706            "reset" => self.handle_reset_method(object_name, object_value, facts),
707            _ => self.handle_property_access_or_fallback(
708                object_name,
709                method,
710                args.len(),
711                &object_value,
712            ),
713        }
714    }
715
716    /// Handle setter method calls (setXxx)
717    fn handle_setter_method(
718        &self,
719        object_name: &str,
720        method: &str,
721        new_value: &Value,
722        mut object_value: Value,
723        facts: &Facts,
724    ) -> Result<String> {
725        let property_name = Self::extract_property_name_from_setter(method);
726
727        match object_value {
728            Value::Object(ref mut obj) => {
729                obj.insert(property_name.clone(), new_value.clone());
730                facts.add_value(object_name, object_value)?;
731                Ok(format!(
732                    "Set {} to {}",
733                    property_name,
734                    new_value.to_string()
735                ))
736            }
737            _ => Err(RuleEngineError::EvaluationError {
738                message: format!("Cannot call setter on non-object type: {}", object_name),
739            }),
740        }
741    }
742
743    /// Handle getter method calls (getXxx)
744    fn handle_getter_method(
745        &self,
746        object_name: &str,
747        method: &str,
748        object_value: &Value,
749    ) -> Result<String> {
750        let property_name = Self::extract_property_name_from_getter(method);
751
752        match object_value {
753            Value::Object(obj) => {
754                if let Some(value) = obj.get(&property_name) {
755                    Ok(value.to_string())
756                } else {
757                    Err(RuleEngineError::EvaluationError {
758                        message: format!(
759                            "Property '{}' not found on object '{}'",
760                            property_name, object_name
761                        ),
762                    })
763                }
764            }
765            _ => Err(RuleEngineError::EvaluationError {
766                message: format!("Cannot call getter on non-object type: {}", object_name),
767            }),
768        }
769    }
770
771    /// Handle reset method call
772    fn handle_reset_method(
773        &self,
774        object_name: &str,
775        mut object_value: Value,
776        facts: &Facts,
777    ) -> Result<String> {
778        match object_value {
779            Value::Object(ref mut obj) => {
780                obj.clear();
781                facts.add_value(object_name, object_value)?;
782                Ok(format!("Reset {}", object_name))
783            }
784            _ => Err(RuleEngineError::EvaluationError {
785                message: format!("Cannot reset non-object type: {}", object_name),
786            }),
787        }
788    }
789
790    /// Handle property access or fallback to generic method call
791    fn handle_property_access_or_fallback(
792        &self,
793        object_name: &str,
794        method: &str,
795        arg_count: usize,
796        object_value: &Value,
797    ) -> Result<String> {
798        if let Value::Object(obj) = object_value {
799            // Try exact property name match
800            if let Some(value) = obj.get(method) {
801                return Ok(value.to_string());
802            }
803
804            // Try capitalized property name
805            let capitalized_method = Self::capitalize_first_letter(method);
806            if let Some(value) = obj.get(&capitalized_method) {
807                return Ok(value.to_string());
808            }
809        }
810
811        // Fallback to generic response
812        Ok(format!(
813            "Called {}.{} with {} args",
814            object_name, method, arg_count
815        ))
816    }
817
818    /// Extract property name from setter method (setXxx -> Xxx)
819    fn extract_property_name_from_setter(method: &str) -> String {
820        let property_name = &method[3..]; // Remove "set" prefix
821        Self::capitalize_first_letter(property_name)
822    }
823
824    /// Extract property name from getter method (getXxx -> Xxx)
825    fn extract_property_name_from_getter(method: &str) -> String {
826        let property_name = &method[3..]; // Remove "get" prefix
827        Self::capitalize_first_letter(property_name)
828    }
829
830    /// Helper function to capitalize first letter of a string
831    fn capitalize_first_letter(s: &str) -> String {
832        if s.is_empty() {
833            return String::new();
834        }
835        let mut chars = s.chars();
836        match chars.next() {
837            None => String::new(),
838            Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
839        }
840    }
841}