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