rust_rule_engine/backward/
search.rs

1//! Search strategies for backward chaining
2
3use super::goal::{Goal, GoalStatus};
4use super::rule_executor::RuleExecutor;
5use std::sync::{Arc, Mutex};
6use crate::rete::propagation::IncrementalEngine;
7use crate::rete::working_memory::FactHandle;
8use crate::Facts;
9use crate::types::Value;
10use crate::KnowledgeBase;
11use crate::engine::rule::Rule;
12use std::collections::VecDeque;
13
14/// Strategy for searching the goal space
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum SearchStrategy {
17    /// Depth-first search (Prolog-style)
18    /// Goes deep into one branch before backtracking
19    DepthFirst,
20    
21    /// Breadth-first search
22    /// Explores all goals at one level before going deeper
23    BreadthFirst,
24    
25    /// Iterative deepening
26    /// Combines benefits of depth-first and breadth-first
27    Iterative,
28}
29
30/// Result of a search operation
31#[derive(Debug)]
32pub struct SearchResult {
33    /// Whether the goal was successfully proven
34    pub success: bool,
35    
36    /// Path taken to prove the goal (sequence of rule names)
37    pub path: Vec<String>,
38    
39    /// Number of goals explored
40    pub goals_explored: usize,
41    
42    /// Maximum depth reached
43    pub max_depth_reached: usize,
44    
45    /// Variable bindings from the proof
46    pub bindings: std::collections::HashMap<String, Value>,
47}
48
49impl SearchResult {
50    /// Create a successful search result
51    pub fn success(path: Vec<String>, goals_explored: usize, max_depth: usize) -> Self {
52        Self {
53            success: true,
54            path,
55            goals_explored,
56            max_depth_reached: max_depth,
57            bindings: std::collections::HashMap::new(),
58        }
59    }
60    
61    /// Create a failed search result
62    pub fn failure(goals_explored: usize, max_depth: usize) -> Self {
63        Self {
64            success: false,
65            path: Vec::new(),
66            goals_explored,
67            max_depth_reached: max_depth,
68            bindings: std::collections::HashMap::new(),
69        }
70    }
71}
72
73/// Depth-first search implementation
74pub struct DepthFirstSearch {
75    max_depth: usize,
76    goals_explored: usize,
77    path: Vec<String>,
78    executor: RuleExecutor,
79}
80
81impl DepthFirstSearch {
82    /// Create a new depth-first search
83    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
84        Self {
85            max_depth,
86            goals_explored: 0,
87            path: Vec::new(),
88            executor: RuleExecutor::new_with_inserter(kb, None),
89        }
90    }
91
92    /// Create a new depth-first search and wire an optional IncrementalEngine
93    /// to enable TMS logical insertion. The engine is provided as Arc<Mutex<>>
94    /// and the inserter closure will call `insert_logical` on it.
95    pub fn new_with_engine(
96        max_depth: usize,
97        kb: KnowledgeBase,
98        engine: Option<Arc<Mutex<IncrementalEngine>>>,
99    ) -> Self {
100        // Build inserter closure if engine is provided
101        let inserter = engine.map(|eng| {
102            let eng = eng.clone();
103            std::sync::Arc::new(move |fact_type: String, data: crate::rete::TypedFacts, rule_name: String, premises: Vec<String>| {
104                if let Ok(mut e) = eng.lock() {
105                    // Resolve premise keys into FactHandles when possible
106                    let handles = e.resolve_premise_keys(premises);
107                    let _ = e.insert_logical(fact_type, data, rule_name, handles);
108                }
109            }) as std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>
110        });
111
112        Self {
113            max_depth,
114            goals_explored: 0,
115            path: Vec::new(),
116            executor: RuleExecutor::new_with_inserter(kb, inserter),
117        }
118    }
119    
120    /// Search for a proof of the goal WITH rule execution
121    pub fn search_with_execution(&mut self, goal: &mut Goal, facts: &mut Facts, kb: &KnowledgeBase) -> SearchResult {
122        self.goals_explored = 0;
123        self.path.clear();
124
125        let success = self.search_recursive_with_execution(goal, facts, kb, 0);
126
127        SearchResult {
128            success,
129            path: self.path.clone(),
130            goals_explored: self.goals_explored,
131            max_depth_reached: goal.depth,
132            bindings: goal.bindings.to_map(),
133        }
134    }
135    
136    /// Search for a proof of the goal (old method, kept for compatibility)
137    pub fn search(&mut self, goal: &mut Goal, _facts: &Facts) -> SearchResult {
138        self.goals_explored = 0;
139        self.path.clear();
140        
141        let success = self.search_recursive(goal, 0);
142        
143        SearchResult {
144            success,
145            path: self.path.clone(),
146            goals_explored: self.goals_explored,
147            max_depth_reached: goal.depth,
148            bindings: goal.bindings.to_map(),
149        }
150    }
151    
152    /// NEW: Recursive search WITH rule execution
153    fn search_recursive_with_execution(
154        &mut self,
155        goal: &mut Goal,
156        facts: &mut Facts,  // ✅ Made mutable to allow rule execution
157        kb: &KnowledgeBase,
158        depth: usize
159    ) -> bool {
160        self.goals_explored += 1;
161
162        // Check depth limit
163        if depth > self.max_depth {
164            goal.status = GoalStatus::Unprovable;
165            return false;
166        }
167
168        // Check if goal already satisfied by existing facts
169        if self.check_goal_in_facts(goal, facts) {
170            goal.status = GoalStatus::Proven;
171            return true;
172        }
173
174        // Check for cycles
175        if goal.status == GoalStatus::InProgress {
176            goal.status = GoalStatus::Unprovable;
177            return false;
178        }
179
180        goal.status = GoalStatus::InProgress;
181        goal.depth = depth;
182
183        // Try each candidate rule
184        for rule_name in goal.candidate_rules.clone() {
185            self.path.push(rule_name.clone());
186
187            // Start an undo frame before trying this candidate so we can rollback
188            // any speculative changes if this candidate doesn't lead to a proof.
189            facts.begin_undo_frame();
190
191            // Get the rule from KB
192            if let Some(rule) = kb.get_rule(&rule_name) {
193                // Try to execute rule (checks conditions AND executes actions)
194                match self.executor.try_execute_rule(&rule, facts) {
195                    Ok(true) => {
196                        // Rule executed successfully - derived new facts!
197                        // Now check if our goal is proven
198                        if self.check_goal_in_facts(goal, facts) {
199                            goal.status = GoalStatus::Proven;
200                            return true; // keep changes
201                        }
202                        // Not proven yet: fallthrough and rollback below
203                    }
204                    Ok(false) => {
205                        // Conditions not satisfied - try to prove them recursively!
206                        if self.try_prove_rule_conditions(&rule, facts, kb, depth + 1) {
207                            // All conditions now satisfied! Try executing rule again
208                            match self.executor.try_execute_rule(&rule, facts) {
209                                Ok(true) => {
210                                    if self.check_goal_in_facts(goal, facts) {
211                                        goal.status = GoalStatus::Proven;
212                                        return true; // keep changes
213                                    }
214                                }
215                                _ => {
216                                    // execution failed on second attempt
217                                }
218                            }
219                        }
220                    }
221                    Err(_) => {
222                        // Execution error - continue to next rule
223                    }
224                }
225            }
226
227            // Candidate failed to prove goal — rollback speculative changes
228            facts.rollback_undo_frame();
229            self.path.pop();
230        }
231
232        // Try sub-goals (begin undo frame before attempting sub-goals so we can rollback
233        // if any sub-goal fails)
234        let mut all_subgoals_proven = true;
235        if !goal.sub_goals.is_empty() {
236            facts.begin_undo_frame();
237            for sub_goal in &mut goal.sub_goals {
238                if !self.search_recursive_with_execution(sub_goal, facts, kb, depth + 1) {
239                    all_subgoals_proven = false;
240                    break;
241                }
242            }
243
244            if all_subgoals_proven {
245                // commit sub-goal frame (keep changes)
246                facts.commit_undo_frame();
247                goal.status = GoalStatus::Proven;
248                return true;
249            }
250
251            // rollback any changes from failed sub-goal exploration
252            facts.rollback_undo_frame();
253        }
254
255        // If we have no candidate rules and no sub-goals, or nothing worked
256        goal.status = GoalStatus::Unprovable;
257        false
258    }
259    
260    /// Check if goal is already satisfied by facts
261    ///
262    /// This method now reuses ConditionEvaluator for proper evaluation
263    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
264        // Parse goal pattern into a Condition and use ConditionEvaluator
265        if let Some(condition) = self.parse_goal_pattern(&goal.pattern) {
266            // Use RuleExecutor's evaluator (which delegates to ConditionEvaluator)
267            self.executor.evaluate_condition(&condition, facts).unwrap_or(false)
268        } else {
269            false
270        }
271    }
272
273    /// Parse goal pattern string into a Condition object
274    ///
275    /// Examples:
276    /// - "User.Score >= 80" → Condition { field: "User.Score", operator: GreaterThanOrEqual, value: Number(80) }
277    /// - "User.IsVIP == true" → Condition { field: "User.IsVIP", operator: Equal, value: Boolean(true) }
278    fn parse_goal_pattern(&self, pattern: &str) -> Option<crate::engine::rule::Condition> {
279        use crate::engine::rule::{Condition, ConditionExpression};
280        use crate::types::Operator;
281
282        // Try parsing operators in order (longest first to avoid conflicts)
283        let operators = [
284            (">=", Operator::GreaterThanOrEqual),
285            ("<=", Operator::LessThanOrEqual),
286            ("==", Operator::Equal),
287            ("!=", Operator::NotEqual),
288            (" > ", Operator::GreaterThan),
289            (" < ", Operator::LessThan),
290            (" contains ", Operator::Contains),
291            (" not_contains ", Operator::NotContains),
292            (" starts_with ", Operator::StartsWith),
293            (" startsWith ", Operator::StartsWith),
294            (" ends_with ", Operator::EndsWith),
295            (" endsWith ", Operator::EndsWith),
296            (" matches ", Operator::Matches),
297        ];
298
299        for (op_str, operator) in operators {
300            if let Some(pos) = pattern.find(op_str) {
301                let field = pattern[..pos].trim().to_string();
302                let value_str = pattern[pos + op_str.len()..].trim();
303
304                // Parse value
305                let value = self.parse_value_string(value_str);
306
307                return Some(Condition {
308                    field: field.clone(),
309                    expression: ConditionExpression::Field(field),
310                    operator,
311                    value,
312                });
313            }
314        }
315
316        None
317    }
318
319    /// Parse value string into a Value
320    fn parse_value_string(&self, s: &str) -> Value {
321        let s = s.trim();
322
323        // Boolean
324        if s == "true" {
325            return Value::Boolean(true);
326        }
327        if s == "false" {
328            return Value::Boolean(false);
329        }
330
331        // Null
332        if s == "null" {
333            return Value::Null;
334        }
335
336        // String (quoted)
337        if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
338            return Value::String(s[1..s.len()-1].to_string());
339        }
340
341        // Number (float)
342        if let Ok(n) = s.parse::<f64>() {
343            return Value::Number(n);
344        }
345
346        // Integer
347        if let Ok(i) = s.parse::<i64>() {
348            return Value::Integer(i);
349        }
350
351        // Default to string
352        Value::String(s.to_string())
353    }
354
355    /// Try to prove all conditions of a rule by creating sub-goals
356    /// This is the core of recursive backward chaining!
357    fn try_prove_rule_conditions(
358        &mut self,
359        rule: &Rule,
360        facts: &mut Facts,
361        kb: &KnowledgeBase,
362        depth: usize,
363    ) -> bool {
364        // Extract all conditions from the condition group and try to prove them
365        self.try_prove_condition_group(&rule.conditions, facts, kb, depth)
366    }
367
368    /// Recursively prove a condition group
369    fn try_prove_condition_group(
370        &mut self,
371        group: &crate::engine::rule::ConditionGroup,
372        facts: &mut Facts,
373        kb: &KnowledgeBase,
374        depth: usize,
375    ) -> bool {
376        use crate::engine::rule::ConditionGroup;
377
378        match group {
379            ConditionGroup::Single(condition) => {
380                // Try to prove this single condition
381                self.try_prove_single_condition(condition, facts, kb, depth)
382            }
383            ConditionGroup::Compound { left, operator, right } => {
384                // Handle AND/OR/NOT logic
385                use crate::types::LogicalOperator;
386                match operator {
387                    LogicalOperator::And => {
388                        // Both must be proven
389                        self.try_prove_condition_group(left, facts, kb, depth)
390                            && self.try_prove_condition_group(right, facts, kb, depth)
391                    }
392                    LogicalOperator::Or => {
393                        // At least one must be proven
394                        self.try_prove_condition_group(left, facts, kb, depth)
395                            || self.try_prove_condition_group(right, facts, kb, depth)
396                    }
397                    LogicalOperator::Not => {
398                        // Left should fail, right doesn't apply
399                        !self.try_prove_condition_group(left, facts, kb, depth)
400                    }
401                }
402            }
403            ConditionGroup::Not(_) | ConditionGroup::Exists(_) | ConditionGroup::Forall(_) | ConditionGroup::Accumulate { .. } => {
404                // Complex conditions (Not, Exists, Forall, Accumulate) cannot be proven backward;
405                // they can only be evaluated against current facts.
406                // Use the executor's condition evaluator to check them.
407                self.executor.evaluate_conditions(group, facts).unwrap_or(false)
408            }
409        }
410    }
411
412    /// Try to prove a single condition
413    fn try_prove_single_condition(
414        &mut self,
415        condition: &crate::engine::rule::Condition,
416        facts: &mut Facts,
417        kb: &KnowledgeBase,
418        depth: usize,
419    ) -> bool {
420        // First check if condition is already satisfied by facts
421        if let Ok(satisfied) = self.executor.evaluate_condition(condition, facts) {
422            if satisfied {
423                return true;
424            }
425        }
426
427        // Condition not satisfied - try to prove it by finding rules that can derive it
428        let goal_pattern = self.condition_to_goal_pattern(condition);
429
430        // Create a sub-goal for this condition
431        let mut sub_goal = Goal::new(goal_pattern.clone());
432        sub_goal.depth = depth;
433
434        // Find candidate rules that could prove this sub-goal
435        for candidate_rule in kb.get_rules() {
436            if self.rule_could_prove_pattern(&candidate_rule, &goal_pattern) {
437                sub_goal.add_candidate_rule(candidate_rule.name.clone());
438            }
439        }
440
441        // Try to prove this sub-goal recursively
442        self.search_recursive_with_execution(&mut sub_goal, facts, kb, depth)
443    }
444
445    /// Convert a condition to a goal pattern string
446    fn condition_to_goal_pattern(&self, condition: &crate::engine::rule::Condition) -> String {
447        use crate::engine::rule::ConditionExpression;
448
449        let field = match &condition.expression {
450            ConditionExpression::Field(f) => f.clone(),
451            ConditionExpression::FunctionCall { name, .. } => name.clone(),
452            ConditionExpression::Test { name, .. } => format!("test({})", name),
453            ConditionExpression::MultiField { field, .. } => field.clone(),
454        };
455
456        let op_str = match condition.operator {
457            crate::types::Operator::Equal => "==",
458            crate::types::Operator::NotEqual => "!=",
459            crate::types::Operator::GreaterThan => ">",
460            crate::types::Operator::LessThan => "<",
461            crate::types::Operator::GreaterThanOrEqual => ">=",
462            crate::types::Operator::LessThanOrEqual => "<=",
463            crate::types::Operator::Contains => "contains",
464            crate::types::Operator::NotContains => "not_contains",
465            crate::types::Operator::StartsWith => "starts_with",
466            crate::types::Operator::EndsWith => "ends_with",
467            crate::types::Operator::Matches => "matches",
468        };
469
470        // Convert value to string format that matches goal patterns
471        let value_str = match &condition.value {
472            Value::Boolean(b) => b.to_string(),
473            Value::Number(n) => n.to_string(),
474            Value::Integer(i) => i.to_string(),
475            Value::String(s) => format!("\"{}\"", s),
476            Value::Null => "null".to_string(),
477            _ => format!("{:?}", condition.value),
478        };
479
480        format!("{} {} {}", field, op_str, value_str)
481    }
482
483    /// Check if a rule could prove a specific goal pattern
484    fn rule_could_prove_pattern(&self, rule: &Rule, pattern: &str) -> bool {
485        // Simple heuristic: check if pattern mentions fields that this rule sets
486        for action in &rule.actions {
487            match action {
488                crate::types::ActionType::Set { field, .. } => {
489                    if pattern.contains(field) {
490                        return true;
491                    }
492                }
493                crate::types::ActionType::MethodCall { object, method, .. } => {
494                    if pattern.contains(object) || pattern.contains(method) {
495                        return true;
496                    }
497                }
498                _ => {}
499            }
500        }
501        false
502    }
503
504    /// OLD: Recursive search without execution
505    fn search_recursive(&mut self, goal: &mut Goal, depth: usize) -> bool {
506        self.goals_explored += 1;
507        
508        // Check depth limit
509        if depth > self.max_depth {
510            goal.status = GoalStatus::Unprovable;
511            return false;
512        }
513        
514        // Check for cycles (goal already in progress)
515        if goal.status == GoalStatus::InProgress {
516            goal.status = GoalStatus::Unprovable;
517            return false;
518        }
519        
520        // Mark as in progress to detect cycles
521        goal.status = GoalStatus::InProgress;
522        goal.depth = depth;
523        
524        // Try each candidate rule
525        for rule_name in goal.candidate_rules.clone() {
526            self.path.push(rule_name.clone());
527            
528            // Get the rule from knowledge base (via goal's stored rules)
529            // In a full implementation with KB access:
530            // 1. Get rule conditions
531            // 2. Check if conditions match current facts
532            // 3. If match, execute rule actions to derive new facts
533            // 4. Mark goal as proven
534            
535            // For backward chaining, we check:
536            // - Can this rule's conclusion prove our goal?
537            // - Are all rule conditions satisfied (recursively)?
538            
539            // Since we found a candidate rule, assume it can prove the goal
540            // The rule was added as candidate because its conclusion matches the goal
541            goal.status = GoalStatus::Proven;
542            return true;
543        }
544        
545        // Try to prove sub-goals
546        for sub_goal in &mut goal.sub_goals {
547            if !self.search_recursive(sub_goal, depth + 1) {
548                goal.status = GoalStatus::Unprovable;
549                return false;
550            }
551        }
552        
553        // If we have no sub-goals and no candidate rules, unprovable
554        if goal.sub_goals.is_empty() && goal.candidate_rules.is_empty() {
555            goal.status = GoalStatus::Unprovable;
556            return false;
557        }
558        
559        goal.status = GoalStatus::Proven;
560        true
561    }
562}
563
564/// Breadth-first search implementation
565pub struct BreadthFirstSearch {
566    max_depth: usize,
567    goals_explored: usize,
568    executor: RuleExecutor,
569}
570
571/// Iterative deepening search implementation
572pub struct IterativeDeepeningSearch {
573    max_depth: usize,
574    goals_explored: usize,
575    kb: KnowledgeBase,
576    engine: Option<Arc<Mutex<IncrementalEngine>>>,
577}
578
579impl IterativeDeepeningSearch {
580    /// Create a new iterative deepening search
581    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
582        Self { max_depth, goals_explored: 0, kb, engine: None }
583    }
584
585    /// Create with optional IncrementalEngine for TMS integration
586    pub fn new_with_engine(max_depth: usize, kb: KnowledgeBase, engine: Option<Arc<Mutex<IncrementalEngine>>>) -> Self {
587        // Store the engine so we can pass it to DFS instances
588        Self { max_depth, goals_explored: 0, kb, engine }
589    }
590
591    /// Search with execution: probe with increasing depth using non-executing DFS,
592    /// then run a final executing DFS at the discovered depth to mutate facts.
593    pub fn search_with_execution(&mut self, root_goal: &mut Goal, facts: &mut Facts, kb: &KnowledgeBase) -> SearchResult {
594        self.goals_explored = 0;
595        let mut cumulative_goals = 0usize;
596
597        // Try increasing depth limits
598        for depth_limit in 0..=self.max_depth {
599            // Probe using a non-executing depth-first search on a cloned goal
600            let mut probe_goal = root_goal.clone();
601            let probe_kb = self.kb.clone();
602            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
603            let probe_result = probe_dfs.search(&mut probe_goal, facts);
604            cumulative_goals += probe_result.goals_explored;
605
606            if probe_result.success {
607                // Found a depth where a proof exists; execute for real at this depth
608                let exec_kb = self.kb.clone();
609                let mut exec_dfs = DepthFirstSearch::new_with_engine(depth_limit, exec_kb, self.engine.clone());
610                let exec_result = exec_dfs.search_with_execution(root_goal, facts, kb);
611                // Aggregate explored goals
612                let mut final_result = exec_result;
613                final_result.goals_explored += cumulative_goals - final_result.goals_explored;
614                return final_result;
615            }
616        }
617
618        // Nothing proved up to max_depth
619        SearchResult::failure(cumulative_goals, self.max_depth)
620    }
621
622    /// Non-executing search using iterative deepening (probes only)
623    pub fn search(&mut self, root_goal: &mut Goal, facts: &Facts) -> SearchResult {
624        self.goals_explored = 0;
625        let mut cumulative_goals = 0usize;
626
627        for depth_limit in 0..=self.max_depth {
628            let mut probe_goal = root_goal.clone();
629            let probe_kb = self.kb.clone();
630            let mut probe_dfs = DepthFirstSearch::new(depth_limit, probe_kb);
631            let probe_result = probe_dfs.search(&mut probe_goal, facts);
632            cumulative_goals += probe_result.goals_explored;
633            if probe_result.success {
634                // Return the successful probe result (with aggregated goals explored)
635                let mut res = probe_result;
636                res.goals_explored = cumulative_goals;
637                return res;
638            }
639        }
640
641        SearchResult::failure(cumulative_goals, self.max_depth)
642    }
643}
644
645impl BreadthFirstSearch {
646    /// Create a new breadth-first search
647    pub fn new(max_depth: usize, kb: KnowledgeBase) -> Self {
648        Self {
649            max_depth,
650            goals_explored: 0,
651            executor: RuleExecutor::new_with_inserter(kb, None),
652        }
653    }
654
655    /// Create BFS with optional engine for TMS integration
656    pub fn new_with_engine(max_depth: usize, kb: KnowledgeBase, engine: Option<Arc<Mutex<IncrementalEngine>>>) -> Self {
657        // If engine provided, create inserter closure
658        let inserter = engine.map(|eng| {
659            let eng = eng.clone();
660            std::sync::Arc::new(move |fact_type: String, data: crate::rete::TypedFacts, rule_name: String, _premises: Vec<String>| {
661                if let Ok(mut e) = eng.lock() {
662                    let _ = e.insert_logical(fact_type, data, rule_name, Vec::new());
663                }
664            }) as std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>
665        });
666
667        Self {
668            max_depth,
669            goals_explored: 0,
670            executor: RuleExecutor::new_with_inserter(kb, inserter),
671        }
672    }
673    
674    /// Search for a proof of the goal using BFS WITH rule execution
675    pub fn search_with_execution(&mut self, root_goal: &mut Goal, facts: &mut Facts, kb: &KnowledgeBase) -> SearchResult {
676        self.goals_explored = 0;
677        let mut queue = VecDeque::new();
678        let mut path = Vec::new();
679        let mut max_depth = 0;
680
681        queue.push_back((root_goal as *mut Goal, 0));
682
683        while let Some((goal_ptr, depth)) = queue.pop_front() {
684            // Safety: We maintain ownership properly
685            let goal = unsafe { &mut *goal_ptr };
686
687            self.goals_explored += 1;
688            max_depth = max_depth.max(depth);
689
690            if depth > self.max_depth {
691                continue;
692            }
693
694            goal.depth = depth;
695
696            // Check if goal already satisfied by facts
697            if self.check_goal_in_facts(goal, facts) {
698                goal.status = GoalStatus::Proven;
699                continue;
700            }
701
702            // Try each candidate rule
703            for rule_name in goal.candidate_rules.clone() {
704                path.push(rule_name.clone());
705
706                // Get the rule from KB
707                if let Some(rule) = kb.get_rule(&rule_name) {
708                    // ✅ FIX: Try to execute rule (checks conditions AND executes actions)
709                    match self.executor.try_execute_rule(&rule, facts) {
710                        Ok(true) => {
711                            // Rule executed successfully - derived new facts!
712                            // Now check if our goal is proven
713                            if self.check_goal_in_facts(goal, facts) {
714                                goal.status = GoalStatus::Proven;
715                                break;
716                            }
717                        }
718                        Ok(false) => {
719                            // Conditions not satisfied - continue to next rule
720                        }
721                        Err(_) => {
722                            // Execution error - continue to next rule
723                        }
724                    }
725                }
726            }
727
728            // Add sub-goals to queue
729            for sub_goal in &mut goal.sub_goals {
730                queue.push_back((sub_goal as *mut Goal, depth + 1));
731            }
732        }
733
734        let success = root_goal.is_proven();
735
736        SearchResult {
737            success,
738            path,
739            goals_explored: self.goals_explored,
740            max_depth_reached: max_depth,
741            bindings: root_goal.bindings.to_map(),
742        }
743    }
744    
745    /// Check if goal is already satisfied by facts
746    ///
747    /// This method now reuses ConditionEvaluator for proper evaluation
748    fn check_goal_in_facts(&self, goal: &Goal, facts: &Facts) -> bool {
749        // Parse goal pattern into a Condition and use ConditionEvaluator
750        if let Some(condition) = self.parse_goal_pattern(&goal.pattern) {
751            // Use RuleExecutor's evaluator (which delegates to ConditionEvaluator)
752            self.executor.evaluate_condition(&condition, facts).unwrap_or(false)
753        } else {
754            false
755        }
756    }
757
758    /// Parse goal pattern string into a Condition object
759    ///
760    /// Examples:
761    /// - "User.Score >= 80" → Condition { field: "User.Score", operator: GreaterThanOrEqual, value: Number(80) }
762    /// - "User.IsVIP == true" → Condition { field: "User.IsVIP", operator: Equal, value: Boolean(true) }
763    fn parse_goal_pattern(&self, pattern: &str) -> Option<crate::engine::rule::Condition> {
764        use crate::engine::rule::{Condition, ConditionExpression};
765        use crate::types::Operator;
766
767        // Try parsing operators in order (longest first to avoid conflicts)
768        let operators = [
769            (">=", Operator::GreaterThanOrEqual),
770            ("<=", Operator::LessThanOrEqual),
771            ("==", Operator::Equal),
772            ("!=", Operator::NotEqual),
773            (" > ", Operator::GreaterThan),
774            (" < ", Operator::LessThan),
775            (" contains ", Operator::Contains),
776            (" not_contains ", Operator::NotContains),
777            (" starts_with ", Operator::StartsWith),
778            (" startsWith ", Operator::StartsWith),
779            (" ends_with ", Operator::EndsWith),
780            (" endsWith ", Operator::EndsWith),
781            (" matches ", Operator::Matches),
782        ];
783
784        for (op_str, operator) in operators {
785            if let Some(pos) = pattern.find(op_str) {
786                let field = pattern[..pos].trim().to_string();
787                let value_str = pattern[pos + op_str.len()..].trim();
788
789                // Parse value
790                let value = self.parse_value_string(value_str);
791
792                return Some(Condition {
793                    field: field.clone(),
794                    expression: ConditionExpression::Field(field),
795                    operator,
796                    value,
797                });
798            }
799        }
800
801        None
802    }
803
804    /// Parse value string into a Value
805    fn parse_value_string(&self, s: &str) -> Value {
806        let s = s.trim();
807
808        // Boolean
809        if s == "true" {
810            return Value::Boolean(true);
811        }
812        if s == "false" {
813            return Value::Boolean(false);
814        }
815
816        // Null
817        if s == "null" {
818            return Value::Null;
819        }
820
821        // String (quoted)
822        if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
823            return Value::String(s[1..s.len()-1].to_string());
824        }
825
826        // Number (float)
827        if let Ok(n) = s.parse::<f64>() {
828            return Value::Number(n);
829        }
830
831        // Integer
832        if let Ok(i) = s.parse::<i64>() {
833            return Value::Integer(i);
834        }
835
836        // Default to string
837        Value::String(s.to_string())
838    }
839    
840    /// Search for a proof of the goal using BFS (old method, kept for compatibility)
841    pub fn search(&mut self, root_goal: &mut Goal, _facts: &Facts) -> SearchResult {
842        self.goals_explored = 0;
843        let mut queue = VecDeque::new();
844        let mut path = Vec::new();
845        let mut max_depth = 0;
846        
847        queue.push_back((root_goal as *mut Goal, 0));
848        
849        while let Some((goal_ptr, depth)) = queue.pop_front() {
850            // Safety: We maintain ownership properly
851            let goal = unsafe { &mut *goal_ptr };
852            
853            self.goals_explored += 1;
854            max_depth = max_depth.max(depth);
855            
856            if depth > self.max_depth {
857                continue;
858            }
859            
860            goal.depth = depth;
861            
862            // Process candidate rules
863            for rule_name in &goal.candidate_rules {
864                path.push(rule_name.clone());
865            }
866            
867            // Add sub-goals to queue
868            for sub_goal in &mut goal.sub_goals {
869                queue.push_back((sub_goal as *mut Goal, depth + 1));
870            }
871            
872            // Check if goal can be proven
873            if !goal.candidate_rules.is_empty() || goal.all_subgoals_proven() {
874                goal.status = GoalStatus::Proven;
875            }
876        }
877        
878        let success = root_goal.is_proven();
879        
880        SearchResult {
881            success,
882            path,
883            goals_explored: self.goals_explored,
884            max_depth_reached: max_depth,
885            bindings: root_goal.bindings.to_map(),
886        }
887    }
888}
889
890#[cfg(test)]
891mod tests {
892    use super::*;
893    
894    #[test]
895    fn test_search_strategies() {
896        assert_eq!(SearchStrategy::DepthFirst, SearchStrategy::DepthFirst);
897        assert_ne!(SearchStrategy::DepthFirst, SearchStrategy::BreadthFirst);
898    }
899    
900    #[test]
901    fn test_search_result_creation() {
902        let success = SearchResult::success(vec!["Rule1".to_string()], 5, 3);
903        assert!(success.success);
904        assert_eq!(success.path.len(), 1);
905        assert_eq!(success.goals_explored, 5);
906        
907        let failure = SearchResult::failure(10, 5);
908        assert!(!failure.success);
909        assert!(failure.path.is_empty());
910    }
911    
912    #[test]
913    fn test_depth_first_search_creation() {
914        let kb = KnowledgeBase::new("test");
915        let dfs = DepthFirstSearch::new(10, kb);
916        assert_eq!(dfs.max_depth, 10);
917        assert_eq!(dfs.goals_explored, 0);
918    }
919    
920    #[test]
921    fn test_depth_first_search_simple() {
922        let kb = KnowledgeBase::new("test");
923        let mut dfs = DepthFirstSearch::new(10, kb);
924        let facts = Facts::new();
925
926        let mut goal = Goal::new("test".to_string());
927        goal.add_candidate_rule("TestRule".to_string());
928
929        let result = dfs.search(&mut goal, &facts);
930
931        assert!(result.success);
932        assert!(goal.is_proven());
933        assert!(result.goals_explored > 0);
934    }
935    
936    #[test]
937    fn test_breadth_first_search() {
938        let kb = KnowledgeBase::new("test");
939        let mut bfs = BreadthFirstSearch::new(10, kb);
940        let facts = Facts::new();
941
942        let mut goal = Goal::new("test".to_string());
943        goal.add_candidate_rule("TestRule".to_string());
944
945        let result = bfs.search(&mut goal, &facts);
946
947        assert!(result.success);
948        assert_eq!(result.goals_explored, 1);
949    }
950
951    #[test]
952    fn test_iterative_deepening_search_success() {
953        let kb = KnowledgeBase::new("test");
954        let mut ids = IterativeDeepeningSearch::new(5, kb.clone());
955        let mut root = Goal::new("test".to_string());
956        root.add_candidate_rule("TestRule".to_string());
957
958        // Facts empty; DFS probe should succeed because candidate rules mark provable
959        let mut facts = Facts::new();
960        let res = ids.search(&mut root, &facts);
961        assert!(res.success);
962    }
963
964    #[test]
965    fn test_iterative_deepening_search_depth_limit() {
966        let kb = KnowledgeBase::new("test");
967        // Set max_depth to 0 so even shallow proofs that require depth >0 fail
968        let mut ids = IterativeDeepeningSearch::new(0, kb.clone());
969        let mut root = Goal::new("test".to_string());
970        // Add a subgoal to force depth > 0
971        let mut sub = Goal::new("sub".to_string());
972        sub.add_candidate_rule("SubRule".to_string());
973        root.sub_goals.push(sub);
974
975        let facts = Facts::new();
976        let res = ids.search(&mut root, &facts);
977        assert!(!res.success);
978    }
979}