logicaffeine_proof/
engine.rs

1//! Backward chaining proof engine.
2//!
3//! "The machine that crawls backward from the Conclusion to the Axioms."
4//!
5//! This module implements the core proof search algorithm. It takes inference
6//! rules and *hunts* for proofs using backward chaining and unification.
7//!
8//! ## Backward Chaining Strategy
9//!
10//! 1. Start with the goal we want to prove
11//! 2. Find rules whose conclusions unify with our goal
12//! 3. Recursively prove the premises of those rules
13//! 4. Build the derivation tree as we succeed
14//!
15//! ## Example
16//!
17//! ```text
18//! Goal: Mortal(socrates)
19//!
20//! Knowledge Base:
21//!   - Human(socrates)
22//!   - ∀x(Human(x) → Mortal(x))
23//!
24//! Search:
25//!   1. Goal matches conclusion of ∀x(Human(x) → Mortal(x)) with x=socrates
26//!   2. New subgoal: Human(socrates)
27//!   3. Human(socrates) matches knowledge base fact
28//!   4. Build derivation tree: ModusPonens(UniversalInst, PremiseMatch)
29//! ```
30
31use crate::error::{ProofError, ProofResult};
32use crate::unify::{
33    apply_subst_to_expr, beta_reduce, compose_substitutions, unify_exprs, unify_pattern,
34    unify_terms, Substitution,
35};
36use crate::{DerivationTree, InferenceRule, ProofExpr, ProofGoal, ProofTerm};
37
38/// Default maximum depth for proof search.
39const DEFAULT_MAX_DEPTH: usize = 100;
40
41/// The backward chaining proof engine.
42///
43/// Searches for proofs by working backwards from the goal, finding rules
44/// whose conclusions match, and recursively proving their premises.
45pub struct BackwardChainer {
46    /// Knowledge base: facts and rules available to the prover.
47    knowledge_base: Vec<ProofExpr>,
48
49    /// Maximum proof depth (prevents infinite loops).
50    max_depth: usize,
51
52    /// Counter for generating fresh variable names.
53    var_counter: usize,
54}
55
56// =============================================================================
57// HELPER FUNCTIONS
58// =============================================================================
59
60/// Convert a ProofTerm to a ProofExpr for reduction.
61///
62/// Terms embed into expressions as atoms or constructors.
63fn term_to_expr(term: &ProofTerm) -> ProofExpr {
64    match term {
65        ProofTerm::Constant(s) => ProofExpr::Atom(s.clone()),
66        ProofTerm::Variable(s) => ProofExpr::Atom(s.clone()),
67        ProofTerm::BoundVarRef(s) => ProofExpr::Atom(s.clone()),
68        ProofTerm::Function(name, args) => {
69            // Check if this is a known constructor
70            if matches!(name.as_str(), "Zero" | "Succ" | "Nil" | "Cons") {
71                ProofExpr::Ctor {
72                    name: name.clone(),
73                    args: args.iter().map(term_to_expr).collect(),
74                }
75            } else {
76                // Otherwise it's a predicate/function
77                ProofExpr::Predicate {
78                    name: name.clone(),
79                    args: args.clone(),
80                    world: None,
81                }
82            }
83        }
84        ProofTerm::Group(terms) => {
85            // Groups become nested predicates or just the single element
86            if terms.len() == 1 {
87                term_to_expr(&terms[0])
88            } else {
89                // Multi-term groups - convert to predicate
90                ProofExpr::Predicate {
91                    name: "Group".into(),
92                    args: terms.clone(),
93                    world: None,
94                }
95            }
96        }
97    }
98}
99
100/// Check if two expressions are structurally equal.
101///
102/// This is syntactic equality after normalization - no unification needed.
103fn exprs_structurally_equal(left: &ProofExpr, right: &ProofExpr) -> bool {
104    match (left, right) {
105        (ProofExpr::Atom(a), ProofExpr::Atom(b)) => a == b,
106
107        (ProofExpr::Ctor { name: n1, args: a1 }, ProofExpr::Ctor { name: n2, args: a2 }) => {
108            n1 == n2 && a1.len() == a2.len() && a1.iter().zip(a2).all(|(x, y)| exprs_structurally_equal(x, y))
109        }
110
111        (
112            ProofExpr::Predicate { name: n1, args: a1, .. },
113            ProofExpr::Predicate { name: n2, args: a2, .. },
114        ) => n1 == n2 && a1.len() == a2.len() && a1.iter().zip(a2).all(|(x, y)| terms_structurally_equal(x, y)),
115
116        (ProofExpr::Identity(l1, r1), ProofExpr::Identity(l2, r2)) => {
117            terms_structurally_equal(l1, l2) && terms_structurally_equal(r1, r2)
118        }
119
120        (ProofExpr::And(l1, r1), ProofExpr::And(l2, r2))
121        | (ProofExpr::Or(l1, r1), ProofExpr::Or(l2, r2))
122        | (ProofExpr::Implies(l1, r1), ProofExpr::Implies(l2, r2))
123        | (ProofExpr::Iff(l1, r1), ProofExpr::Iff(l2, r2)) => {
124            exprs_structurally_equal(l1, l2) && exprs_structurally_equal(r1, r2)
125        }
126
127        (ProofExpr::Not(a), ProofExpr::Not(b)) => exprs_structurally_equal(a, b),
128
129        (
130            ProofExpr::ForAll { variable: v1, body: b1 },
131            ProofExpr::ForAll { variable: v2, body: b2 },
132        )
133        | (
134            ProofExpr::Exists { variable: v1, body: b1 },
135            ProofExpr::Exists { variable: v2, body: b2 },
136        ) => v1 == v2 && exprs_structurally_equal(b1, b2),
137
138        (
139            ProofExpr::Lambda { variable: v1, body: b1 },
140            ProofExpr::Lambda { variable: v2, body: b2 },
141        ) => v1 == v2 && exprs_structurally_equal(b1, b2),
142
143        (ProofExpr::App(f1, a1), ProofExpr::App(f2, a2)) => {
144            exprs_structurally_equal(f1, f2) && exprs_structurally_equal(a1, a2)
145        }
146
147        (
148            ProofExpr::TypedVar { name: n1, typename: t1 },
149            ProofExpr::TypedVar { name: n2, typename: t2 },
150        ) => n1 == n2 && t1 == t2,
151
152        (
153            ProofExpr::Fixpoint { name: n1, body: b1 },
154            ProofExpr::Fixpoint { name: n2, body: b2 },
155        ) => n1 == n2 && exprs_structurally_equal(b1, b2),
156
157        _ => false,
158    }
159}
160
161/// Check if two terms are structurally equal.
162fn terms_structurally_equal(left: &ProofTerm, right: &ProofTerm) -> bool {
163    match (left, right) {
164        (ProofTerm::Constant(a), ProofTerm::Constant(b)) => a == b,
165        (ProofTerm::Variable(a), ProofTerm::Variable(b)) => a == b,
166        (ProofTerm::BoundVarRef(a), ProofTerm::BoundVarRef(b)) => a == b,
167        (ProofTerm::Function(n1, a1), ProofTerm::Function(n2, a2)) => {
168            n1 == n2 && a1.len() == a2.len() && a1.iter().zip(a2).all(|(x, y)| terms_structurally_equal(x, y))
169        }
170        (ProofTerm::Group(a1), ProofTerm::Group(a2)) => {
171            a1.len() == a2.len() && a1.iter().zip(a2).all(|(x, y)| terms_structurally_equal(x, y))
172        }
173        _ => false,
174    }
175}
176
177impl BackwardChainer {
178    /// Create a new proof engine with empty knowledge base.
179    pub fn new() -> Self {
180        Self {
181            knowledge_base: Vec::new(),
182            max_depth: DEFAULT_MAX_DEPTH,
183            var_counter: 0,
184        }
185    }
186
187    /// Set the maximum proof search depth.
188    pub fn set_max_depth(&mut self, depth: usize) {
189        self.max_depth = depth;
190    }
191
192    /// Get a reference to the knowledge base (for debugging).
193    pub fn knowledge_base(&self) -> &[ProofExpr] {
194        &self.knowledge_base
195    }
196
197    /// Add an axiom/fact/rule to the knowledge base.
198    ///
199    /// Event semantics are automatically abstracted to simple predicates for efficient proof search.
200    pub fn add_axiom(&mut self, expr: ProofExpr) {
201        // Pre-process: abstract event semantics to simple predicates
202        let abstracted = self.abstract_all_events(&expr);
203        // Simplify definite description conjunctions (e.g., butler(butler) ∧ P → P)
204        let simplified = self.simplify_definite_description_conjunction(&abstracted);
205        self.knowledge_base.push(simplified);
206    }
207
208    /// Attempt to prove a goal.
209    ///
210    /// Returns a derivation tree if successful, explaining how the proof was constructed.
211    /// Event semantics in the goal are automatically abstracted (but De Morgan is not applied
212    /// to preserve goal pattern matching for reductio strategies).
213    pub fn prove(&mut self, goal: ProofExpr) -> ProofResult<DerivationTree> {
214        // Pre-process: unify definite descriptions across all axioms
215        // This handles Russell's theory of definite descriptions, where multiple
216        // "the X" references should refer to the same entity.
217        self.unify_definite_descriptions();
218
219        // Pre-process: abstract event semantics in the goal
220        // Use abstract_events_only which doesn't apply De Morgan (to preserve ¬∃ pattern)
221        let abstracted_goal = self.abstract_events_only(&goal);
222        // Simplify definite description conjunctions
223        let normalized_goal = self.simplify_definite_description_conjunction(&abstracted_goal);
224        self.prove_goal(ProofGoal::new(normalized_goal), 0)
225    }
226
227    /// Prove a goal with pre-populated context assumptions.
228    ///
229    /// This allows proving goals like "x > 5" given assumptions like "x > 10" in the context.
230    /// The oracle (Z3) will use these context assumptions when verifying.
231    pub fn prove_with_goal(&mut self, goal: ProofGoal) -> ProofResult<DerivationTree> {
232        self.unify_definite_descriptions();
233
234        // Normalize the target
235        let abstracted_target = self.abstract_events_only(&goal.target);
236        let normalized_target = self.simplify_definite_description_conjunction(&abstracted_target);
237
238        // Normalize each context assumption
239        let normalized_context: Vec<ProofExpr> = goal
240            .context
241            .iter()
242            .map(|expr| {
243                let abstracted = self.abstract_events_only(expr);
244                self.simplify_definite_description_conjunction(&abstracted)
245            })
246            .collect();
247
248        let normalized_goal = ProofGoal::with_context(normalized_target, normalized_context);
249        self.prove_goal(normalized_goal, 0)
250    }
251
252    /// Unify definite descriptions across axioms.
253    ///
254    /// When multiple axioms contain the same definite description pattern
255    /// (e.g., "the barber" creates `∃x ((barber(x) ∧ ∀y (barber(y) → y=x)) ∧ P(x))`),
256    /// this function:
257    /// 1. Identifies all axioms with the same defining predicate
258    /// 2. Extracts the properties attributed to the definite description
259    /// 3. Replaces them with a unified Skolem constant and extracted properties
260    fn unify_definite_descriptions(&mut self) {
261        // Collect definite descriptions by their defining predicate
262        let mut definite_descs: std::collections::HashMap<String, Vec<(usize, String, ProofExpr)>> = std::collections::HashMap::new();
263
264        for (idx, axiom) in self.knowledge_base.iter().enumerate() {
265            if let Some((pred_name, var_name, property)) = self.extract_definite_description(axiom) {
266                definite_descs.entry(pred_name).or_default().push((idx, var_name, property));
267            }
268        }
269
270        // For each group of definite descriptions with the same predicate
271        for (pred_name, descs) in definite_descs {
272            if descs.is_empty() {
273                continue;
274            }
275
276            // Create a unified Skolem constant for this definite description
277            let skolem_name = format!("the_{}", pred_name);
278            let skolem_const = ProofTerm::Constant(skolem_name.clone());
279
280            // Add the defining property: pred(skolem)
281            let defining_fact = ProofExpr::Predicate {
282                name: pred_name.clone(),
283                args: vec![skolem_const.clone()],
284                world: None,
285            };
286            self.knowledge_base.push(defining_fact);
287
288            // CRITICAL: Add uniqueness constraint: ∀y (pred(y) → y = skolem)
289            // This is essential for proofs that assume ∃x pred(x) - they need to
290            // unify their Skolem constant with our unified constant.
291            let uniqueness = ProofExpr::ForAll {
292                variable: "_u".to_string(),
293                body: Box::new(ProofExpr::Implies(
294                    Box::new(ProofExpr::Predicate {
295                        name: pred_name.clone(),
296                        args: vec![ProofTerm::Variable("_u".to_string())],
297                        world: None,
298                    }),
299                    Box::new(ProofExpr::Identity(
300                        ProofTerm::Variable("_u".to_string()),
301                        skolem_const.clone(),
302                    )),
303                )),
304            };
305            self.knowledge_base.push(uniqueness);
306
307            // Replace axioms with the extracted properties
308            let mut indices_to_remove: Vec<usize> = Vec::new();
309            for (idx, var_name, property) in descs {
310                // Substitute the original variable with the Skolem constant
311                let substituted = self.substitute_term_in_expr(
312                    &property,
313                    &ProofTerm::Variable(var_name),
314                    &skolem_const,
315                );
316                // Normalize the property (especially for ∀x ¬(P ∧ Q) → ∀x (P → ¬Q))
317                let normalized = self.normalize_for_proof(&substituted);
318                self.knowledge_base.push(normalized);
319                indices_to_remove.push(idx);
320            }
321
322            // Remove the original existential axioms (in reverse order to preserve indices)
323            indices_to_remove.sort_unstable_by(|a, b| b.cmp(a));
324            for idx in indices_to_remove {
325                self.knowledge_base.remove(idx);
326            }
327        }
328    }
329
330    /// Normalize an expression for proof search.
331    ///
332    /// Applies transformations like: ∀x ¬(P ∧ Q) → ∀x (P → ¬Q)
333    fn normalize_for_proof(&self, expr: &ProofExpr) -> ProofExpr {
334        match expr {
335            ProofExpr::ForAll { variable, body } => {
336                // Check for pattern: ∀x ¬(P ∧ Q) → ∀x (P → ¬Q)
337                if let ProofExpr::Not(inner) = body.as_ref() {
338                    if let ProofExpr::And(left, right) = inner.as_ref() {
339                        return ProofExpr::ForAll {
340                            variable: variable.clone(),
341                            body: Box::new(ProofExpr::Implies(
342                                Box::new(self.normalize_for_proof(left)),
343                                Box::new(ProofExpr::Not(Box::new(self.normalize_for_proof(right)))),
344                            )),
345                        };
346                    }
347                }
348                ProofExpr::ForAll {
349                    variable: variable.clone(),
350                    body: Box::new(self.normalize_for_proof(body)),
351                }
352            }
353            ProofExpr::And(left, right) => ProofExpr::And(
354                Box::new(self.normalize_for_proof(left)),
355                Box::new(self.normalize_for_proof(right)),
356            ),
357            ProofExpr::Or(left, right) => ProofExpr::Or(
358                Box::new(self.normalize_for_proof(left)),
359                Box::new(self.normalize_for_proof(right)),
360            ),
361            ProofExpr::Implies(left, right) => ProofExpr::Implies(
362                Box::new(self.normalize_for_proof(left)),
363                Box::new(self.normalize_for_proof(right)),
364            ),
365            ProofExpr::Not(inner) => ProofExpr::Not(Box::new(self.normalize_for_proof(inner))),
366            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
367                variable: variable.clone(),
368                body: Box::new(self.normalize_for_proof(body)),
369            },
370            other => other.clone(),
371        }
372    }
373
374    /// Extract a definite description from an axiom.
375    ///
376    /// Pattern: ∃x ((P(x) ∧ ∀y (P(y) → y = x)) ∧ Q(x))
377    /// Returns: Some((predicate_name, variable_name, Q(x)))
378    fn extract_definite_description(&self, expr: &ProofExpr) -> Option<(String, String, ProofExpr)> {
379        // Match: ∃x (body)
380        let (var, body) = match expr {
381            ProofExpr::Exists { variable, body } => (variable.clone(), body.as_ref()),
382            _ => return None,
383        };
384
385        // Match: (defining_part ∧ property)
386        let (defining_part, property) = match body {
387            ProofExpr::And(left, right) => (left.as_ref(), right.as_ref().clone()),
388            _ => return None,
389        };
390
391        // Match defining_part: (P(x) ∧ ∀y (P(y) → y = x))
392        let (type_pred, uniqueness) = match defining_part {
393            ProofExpr::And(left, right) => (left.as_ref(), right.as_ref()),
394            _ => return None,
395        };
396
397        // Extract predicate name from P(x)
398        let pred_name = match type_pred {
399            ProofExpr::Predicate { name, args, .. } if args.len() == 1 => {
400                // Verify the arg is our variable
401                if let ProofTerm::Variable(v) = &args[0] {
402                    if v == &var {
403                        name.clone()
404                    } else {
405                        return None;
406                    }
407                } else {
408                    return None;
409                }
410            }
411            _ => return None,
412        };
413
414        // Verify uniqueness constraint: ∀y (P(y) → y = x)
415        match uniqueness {
416            ProofExpr::ForAll { variable: _, body: inner_body } => {
417                match inner_body.as_ref() {
418                    ProofExpr::Implies(ante, cons) => {
419                        // Verify antecedent is P(y)
420                        if let ProofExpr::Predicate { name, .. } = ante.as_ref() {
421                            if name != &pred_name {
422                                return None;
423                            }
424                        } else {
425                            return None;
426                        }
427                        // Verify consequent is an identity (y = x)
428                        if !matches!(cons.as_ref(), ProofExpr::Identity(_, _)) {
429                            return None;
430                        }
431                    }
432                    _ => return None,
433                }
434            }
435            _ => return None,
436        }
437
438        Some((pred_name, var, property))
439    }
440
441    /// Internal proof search with depth tracking.
442    fn prove_goal(&mut self, goal: ProofGoal, depth: usize) -> ProofResult<DerivationTree> {
443        // Check depth limit
444        if depth > self.max_depth {
445            return Err(ProofError::DepthExceeded);
446        }
447
448        // PRIORITY: Check for inductive goals FIRST
449        // Goals with TypedVar (e.g., n:Nat) require structural induction,
450        // not direct unification which would incorrectly ground the variable.
451        if let Some((_, typename)) = self.find_typed_var(&goal.target) {
452            // For known inductive types, require induction to succeed
453            // Falling back to direct matching would incorrectly unify the TypedVar
454            let is_known_inductive = matches!(typename.as_str(), "Nat" | "List");
455
456            if let Some(tree) = self.try_structural_induction(&goal, depth)? {
457                return Ok(tree);
458            }
459
460            // For known inductive types, if induction fails, the proof fails
461            // (don't allow incorrect direct unification)
462            if is_known_inductive {
463                return Err(ProofError::NoProofFound);
464            }
465            // For unknown types, fall through to other strategies
466        }
467
468        // Strategy 0: Reflexivity by computation
469        // Try to prove a = b by normalizing both sides
470        if let Some(tree) = self.try_reflexivity(&goal)? {
471            return Ok(tree);
472        }
473
474        // Strategy 1: Direct fact matching
475        if let Some(tree) = self.try_match_fact(&goal)? {
476            return Ok(tree);
477        }
478
479        // Strategy 2: Introduction rules (structural decomposition)
480        if let Some(tree) = self.try_intro_rules(&goal, depth)? {
481            return Ok(tree);
482        }
483
484        // Strategy 3: Backward chaining on implications
485        if let Some(tree) = self.try_backward_chain(&goal, depth)? {
486            return Ok(tree);
487        }
488
489        // Strategy 3b: Modus Tollens (from P → Q and ¬Q, derive ¬P)
490        if let Some(tree) = self.try_modus_tollens(&goal, depth)? {
491            return Ok(tree);
492        }
493
494        // Strategy 4: Universal instantiation
495        if let Some(tree) = self.try_universal_inst(&goal, depth)? {
496            return Ok(tree);
497        }
498
499        // Strategy 5: Existential introduction
500        if let Some(tree) = self.try_existential_intro(&goal, depth)? {
501            return Ok(tree);
502        }
503
504        // Strategy 5b: Disjunction elimination (disjunctive syllogism)
505        if let Some(tree) = self.try_disjunction_elimination(&goal, depth)? {
506            return Ok(tree);
507        }
508
509        // Strategy 5c: Proof by contradiction (reductio ad absurdum)
510        // For negation goals, assume the positive and derive contradiction
511        if let Some(tree) = self.try_reductio_ad_absurdum(&goal, depth)? {
512            return Ok(tree);
513        }
514
515        // Strategy 5d: Existential elimination from premises
516        // Extract witnesses from ∃x P(x) premises and add to context
517        if let Some(tree) = self.try_existential_elimination(&goal, depth)? {
518            return Ok(tree);
519        }
520
521        // Strategy 6: Equality rewriting (Leibniz's Law)
522        if let Some(tree) = self.try_equality_rewrite(&goal, depth)? {
523            return Ok(tree);
524        }
525
526        // Strategy 7: Oracle fallback (Z3)
527        #[cfg(feature = "verification")]
528        if let Some(tree) = self.try_oracle_fallback(&goal)? {
529            return Ok(tree);
530        }
531
532        // No proof found
533        Err(ProofError::NoProofFound)
534    }
535
536    // =========================================================================
537    // STRATEGY 0: REFLEXIVITY BY COMPUTATION
538    // =========================================================================
539
540    /// Try to prove an identity a = b by normalizing both sides.
541    ///
542    /// If both sides reduce to structurally identical expressions,
543    /// the proof is by reflexivity (a = a).
544    fn try_reflexivity(&self, goal: &ProofGoal) -> ProofResult<Option<DerivationTree>> {
545        if let ProofExpr::Identity(left_term, right_term) = &goal.target {
546            // Convert terms to expressions for reduction
547            let left_expr = term_to_expr(left_term);
548            let right_expr = term_to_expr(right_term);
549
550            // Normalize both sides using full reduction (beta + iota + fix)
551            let left_normal = beta_reduce(&left_expr);
552            let right_normal = beta_reduce(&right_expr);
553
554            // Check structural equality after normalization
555            if exprs_structurally_equal(&left_normal, &right_normal) {
556                return Ok(Some(DerivationTree::leaf(
557                    goal.target.clone(),
558                    InferenceRule::Reflexivity,
559                )));
560            }
561        }
562        Ok(None)
563    }
564
565    // =========================================================================
566    // STRATEGY 1: DIRECT FACT MATCHING
567    // =========================================================================
568
569    /// Try to match the goal directly against a fact in the knowledge base.
570    fn try_match_fact(&self, goal: &ProofGoal) -> ProofResult<Option<DerivationTree>> {
571        // Also check local context
572        for fact in goal.context.iter().chain(self.knowledge_base.iter()) {
573            if let Ok(subst) = unify_exprs(&goal.target, fact) {
574                return Ok(Some(
575                    DerivationTree::leaf(goal.target.clone(), InferenceRule::PremiseMatch)
576                        .with_substitution(subst),
577                ));
578            }
579        }
580        Ok(None)
581    }
582
583    // =========================================================================
584    // STRATEGY 2: INTRODUCTION RULES
585    // =========================================================================
586
587    /// Try introduction rules based on the goal's structure.
588    fn try_intro_rules(
589        &mut self,
590        goal: &ProofGoal,
591        depth: usize,
592    ) -> ProofResult<Option<DerivationTree>> {
593        match &goal.target {
594            // Conjunction Introduction: To prove A ∧ B, prove A and prove B
595            ProofExpr::And(left, right) => {
596                let left_goal = ProofGoal::with_context((**left).clone(), goal.context.clone());
597                let right_goal = ProofGoal::with_context((**right).clone(), goal.context.clone());
598
599                // Try to prove both sides
600                if let (Ok(left_proof), Ok(right_proof)) = (
601                    self.prove_goal(left_goal, depth + 1),
602                    self.prove_goal(right_goal, depth + 1),
603                ) {
604                    return Ok(Some(DerivationTree::new(
605                        goal.target.clone(),
606                        InferenceRule::ConjunctionIntro,
607                        vec![left_proof, right_proof],
608                    )));
609                }
610            }
611
612            // Disjunction Introduction: To prove A ∨ B, prove A or prove B
613            ProofExpr::Or(left, right) => {
614                // Try left side first
615                let left_goal = ProofGoal::with_context((**left).clone(), goal.context.clone());
616                if let Ok(left_proof) = self.prove_goal(left_goal, depth + 1) {
617                    return Ok(Some(DerivationTree::new(
618                        goal.target.clone(),
619                        InferenceRule::DisjunctionIntro,
620                        vec![left_proof],
621                    )));
622                }
623
624                // Try right side
625                let right_goal = ProofGoal::with_context((**right).clone(), goal.context.clone());
626                if let Ok(right_proof) = self.prove_goal(right_goal, depth + 1) {
627                    return Ok(Some(DerivationTree::new(
628                        goal.target.clone(),
629                        InferenceRule::DisjunctionIntro,
630                        vec![right_proof],
631                    )));
632                }
633            }
634
635            // Double Negation: To prove ¬¬P, prove P
636            ProofExpr::Not(inner) => {
637                if let ProofExpr::Not(core) = &**inner {
638                    let core_goal = ProofGoal::with_context((**core).clone(), goal.context.clone());
639                    if let Ok(core_proof) = self.prove_goal(core_goal, depth + 1) {
640                        return Ok(Some(DerivationTree::new(
641                            goal.target.clone(),
642                            InferenceRule::DoubleNegation,
643                            vec![core_proof],
644                        )));
645                    }
646                }
647            }
648
649            _ => {}
650        }
651
652        Ok(None)
653    }
654
655    // =========================================================================
656    // STRATEGY 3: BACKWARD CHAINING ON IMPLICATIONS
657    // =========================================================================
658
659    /// Try backward chaining: find P → Goal in KB, then prove P.
660    fn try_backward_chain(
661        &mut self,
662        goal: &ProofGoal,
663        depth: usize,
664    ) -> ProofResult<Option<DerivationTree>> {
665        // Collect implications from KB (we need to clone to avoid borrow issues)
666        let implications: Vec<(usize, ProofExpr)> = self
667            .knowledge_base
668            .iter()
669            .enumerate()
670            .filter_map(|(idx, expr)| {
671                if let ProofExpr::Implies(_, _) = expr {
672                    Some((idx, expr.clone()))
673                } else {
674                    None
675                }
676            })
677            .collect();
678
679        for (idx, impl_expr) in implications {
680            if let ProofExpr::Implies(antecedent, consequent) = &impl_expr {
681                // Rename variables to avoid capture
682                let renamed = self.rename_variables(&impl_expr);
683                if let ProofExpr::Implies(ant, con) = renamed {
684                    // Try to unify the consequent with our goal
685                    if let Ok(subst) = unify_exprs(&goal.target, &con) {
686                        // Apply substitution to the antecedent
687                        let new_antecedent = apply_subst_to_expr(&ant, &subst);
688
689                        // Try to prove the antecedent
690                        let ant_goal =
691                            ProofGoal::with_context(new_antecedent, goal.context.clone());
692
693                        if let Ok(ant_proof) = self.prove_goal(ant_goal, depth + 1) {
694                            // Success! Build the modus ponens tree
695                            let impl_leaf = DerivationTree::leaf(
696                                impl_expr.clone(),
697                                InferenceRule::PremiseMatch,
698                            );
699
700                            return Ok(Some(
701                                DerivationTree::new(
702                                    goal.target.clone(),
703                                    InferenceRule::ModusPonens,
704                                    vec![impl_leaf, ant_proof],
705                                )
706                                .with_substitution(subst),
707                            ));
708                        }
709                    }
710                }
711            }
712        }
713
714        Ok(None)
715    }
716
717    // =========================================================================
718    // STRATEGY 3b: MODUS TOLLENS
719    // =========================================================================
720
721    /// Try Modus Tollens: from P → Q and ¬Q, derive ¬P.
722    ///
723    /// If the goal is ¬P:
724    /// 1. Look for implications P → Q in the KB
725    /// 2. Check if ¬Q is known (in KB or context) OR can be proved
726    /// 3. If so, derive ¬P by Modus Tollens
727    fn try_modus_tollens(
728        &mut self,
729        goal: &ProofGoal,
730        depth: usize,
731    ) -> ProofResult<Option<DerivationTree>> {
732        // Modus Tollens only applies when goal is a negation: ¬P
733        let inner_goal = match &goal.target {
734            ProofExpr::Not(inner) => (**inner).clone(),
735            _ => return Ok(None),
736        };
737
738        // Collect all implications from KB, including those inside ForAll
739        let implications: Vec<ProofExpr> = self
740            .knowledge_base
741            .iter()
742            .chain(goal.context.iter())
743            .flat_map(|expr| {
744                match expr {
745                    ProofExpr::Implies(_, _) => vec![expr.clone()],
746                    ProofExpr::ForAll { body, .. } => {
747                        // Extract implications from inside universal quantifiers
748                        if let ProofExpr::Implies(_, _) = body.as_ref() {
749                            vec![*body.clone()]
750                        } else {
751                            vec![]
752                        }
753                    }
754                    _ => vec![],
755                }
756            })
757            .collect();
758
759        // Collect all negations from KB and context (for direct matching)
760        let negations: Vec<ProofExpr> = self
761            .knowledge_base
762            .iter()
763            .chain(goal.context.iter())
764            .filter_map(|expr| {
765                if let ProofExpr::Not(inner) = expr {
766                    Some((**inner).clone())
767                } else {
768                    None
769                }
770            })
771            .collect();
772
773        // For each implication P → Q
774        for impl_expr in &implications {
775            if let ProofExpr::Implies(antecedent, consequent) = impl_expr {
776                // Check if the antecedent P matches our inner goal (we want to prove ¬P)
777                if let Ok(subst) = unify_exprs(&inner_goal, antecedent) {
778                    // Apply substitution to the consequent Q
779                    let q = apply_subst_to_expr(consequent, &subst);
780
781                    // First, check if ¬Q is directly in our known facts
782                    for negated in &negations {
783                        if exprs_structurally_equal(negated, &q) {
784                            // We have P → Q and ¬Q, so we can derive ¬P
785                            let impl_leaf = DerivationTree::leaf(
786                                impl_expr.clone(),
787                                InferenceRule::PremiseMatch,
788                            );
789                            let neg_q_leaf = DerivationTree::leaf(
790                                ProofExpr::Not(Box::new(q.clone())),
791                                InferenceRule::PremiseMatch,
792                            );
793
794                            return Ok(Some(
795                                DerivationTree::new(
796                                    goal.target.clone(),
797                                    InferenceRule::ModusTollens,
798                                    vec![impl_leaf, neg_q_leaf],
799                                )
800                                .with_substitution(subst),
801                            ));
802                        }
803                    }
804
805                    // Second, try to prove ¬Q recursively (for chaining)
806                    let neg_q_goal = ProofGoal::with_context(
807                        ProofExpr::Not(Box::new(q.clone())),
808                        goal.context.clone(),
809                    );
810
811                    if let Ok(neg_q_proof) = self.prove_goal(neg_q_goal, depth + 1) {
812                        // We proved ¬Q, so we can derive ¬P
813                        let impl_leaf = DerivationTree::leaf(
814                            impl_expr.clone(),
815                            InferenceRule::PremiseMatch,
816                        );
817
818                        return Ok(Some(
819                            DerivationTree::new(
820                                goal.target.clone(),
821                                InferenceRule::ModusTollens,
822                                vec![impl_leaf, neg_q_proof],
823                            )
824                            .with_substitution(subst),
825                        ));
826                    }
827                }
828            }
829        }
830
831        Ok(None)
832    }
833
834    // =========================================================================
835    // STRATEGY 4: UNIVERSAL INSTANTIATION
836    // =========================================================================
837
838    /// Try universal instantiation: if KB has ∀x.P(x), try to prove P(t) for some term t.
839    fn try_universal_inst(
840        &mut self,
841        goal: &ProofGoal,
842        depth: usize,
843    ) -> ProofResult<Option<DerivationTree>> {
844        // Look for universal quantifiers in KB
845        let universals: Vec<(usize, ProofExpr)> = self
846            .knowledge_base
847            .iter()
848            .enumerate()
849            .filter_map(|(idx, expr)| {
850                if let ProofExpr::ForAll { .. } = expr {
851                    Some((idx, expr.clone()))
852                } else {
853                    None
854                }
855            })
856            .collect();
857
858        for (idx, forall_expr) in universals {
859            if let ProofExpr::ForAll { variable, body } = &forall_expr {
860                // Rename to avoid capture
861                let renamed = self.rename_variables(&forall_expr);
862                if let ProofExpr::ForAll {
863                    variable: var,
864                    body: renamed_body,
865                } = renamed
866                {
867                    // Try to unify the body with our goal
868                    if let Ok(subst) = unify_exprs(&goal.target, &renamed_body) {
869                        // Check if we found a value for the quantified variable
870                        if let Some(witness) = subst.get(&var) {
871                            let witness_str = format!("{}", witness);
872
873                            // The universal premise
874                            let universal_leaf = DerivationTree::leaf(
875                                forall_expr.clone(),
876                                InferenceRule::PremiseMatch,
877                            );
878
879                            return Ok(Some(
880                                DerivationTree::new(
881                                    goal.target.clone(),
882                                    InferenceRule::UniversalInst(witness_str),
883                                    vec![universal_leaf],
884                                )
885                                .with_substitution(subst),
886                            ));
887                        }
888                    }
889
890                    // Also try: if the body is an implication (∀x(P(x) → Q(x))),
891                    // and our goal is Q(t), try to prove P(t)
892                    if let ProofExpr::Implies(ant, con) = &*renamed_body {
893                        if let Ok(subst) = unify_exprs(&goal.target, con) {
894                            // Found a match! Now prove the antecedent
895                            let new_antecedent = apply_subst_to_expr(ant, &subst);
896
897                            let ant_goal =
898                                ProofGoal::with_context(new_antecedent, goal.context.clone());
899
900                            if let Ok(ant_proof) = self.prove_goal(ant_goal, depth + 1) {
901                                // Get the witness from substitution
902                                let witness_str = subst
903                                    .get(&var)
904                                    .map(|t| format!("{}", t))
905                                    .unwrap_or_else(|| var.clone());
906
907                                // Build the proof tree
908                                let universal_leaf = DerivationTree::leaf(
909                                    forall_expr.clone(),
910                                    InferenceRule::PremiseMatch,
911                                );
912
913                                let inst_node = DerivationTree::new(
914                                    apply_subst_to_expr(&renamed_body, &subst),
915                                    InferenceRule::UniversalInst(witness_str),
916                                    vec![universal_leaf],
917                                );
918
919                                return Ok(Some(
920                                    DerivationTree::new(
921                                        goal.target.clone(),
922                                        InferenceRule::ModusPonens,
923                                        vec![inst_node, ant_proof],
924                                    )
925                                    .with_substitution(subst),
926                                ));
927                            }
928                        }
929                    }
930                }
931            }
932        }
933
934        Ok(None)
935    }
936
937    // =========================================================================
938    // STRATEGY 5: EXISTENTIAL INTRODUCTION
939    // =========================================================================
940
941    /// Try existential introduction: to prove ∃x.P(x), find a witness t and prove P(t).
942    fn try_existential_intro(
943        &mut self,
944        goal: &ProofGoal,
945        depth: usize,
946    ) -> ProofResult<Option<DerivationTree>> {
947        if let ProofExpr::Exists { variable, body } = &goal.target {
948            // We need to find a witness that makes the body true
949            // Try each constant/ground term in our KB as a potential witness
950            let witnesses = self.collect_witnesses();
951
952            for witness in witnesses {
953                // Create a substitution mapping the variable to the witness
954                let mut subst = Substitution::new();
955                subst.insert(variable.clone(), witness.clone());
956
957                // Apply substitution to get the instantiated body
958                let instantiated = apply_subst_to_expr(body, &subst);
959
960                // Try to prove the instantiated body
961                let inst_goal = ProofGoal::with_context(instantiated, goal.context.clone());
962
963                if let Ok(body_proof) = self.prove_goal(inst_goal, depth + 1) {
964                    let witness_str = format!("{}", witness);
965                    // Extract witness type from body if available, otherwise default to Nat
966                    let witness_type = extract_type_from_exists_body(body)
967                        .unwrap_or_else(|| "Nat".to_string());
968                    return Ok(Some(DerivationTree::new(
969                        goal.target.clone(),
970                        InferenceRule::ExistentialIntro {
971                            witness: witness_str,
972                            witness_type,
973                        },
974                        vec![body_proof],
975                    )));
976                }
977            }
978        }
979
980        Ok(None)
981    }
982
983    // =========================================================================
984    // STRATEGY 5b: DISJUNCTION ELIMINATION (DISJUNCTIVE SYLLOGISM)
985    // =========================================================================
986
987    /// Try disjunction elimination: if KB has A ∨ B and ¬A, conclude B (and vice versa).
988    ///
989    /// Disjunctive syllogism:
990    /// - From A ∨ B and ¬A, derive B
991    /// - From A ∨ B and ¬B, derive A
992    fn try_disjunction_elimination(
993        &mut self,
994        goal: &ProofGoal,
995        _depth: usize,
996    ) -> ProofResult<Option<DerivationTree>> {
997        // Collect disjunctions from KB and context
998        let disjunctions: Vec<(ProofExpr, ProofExpr, ProofExpr)> = self
999            .knowledge_base
1000            .iter()
1001            .chain(goal.context.iter())
1002            .filter_map(|expr| {
1003                if let ProofExpr::Or(left, right) = expr {
1004                    Some((expr.clone(), (**left).clone(), (**right).clone()))
1005                } else {
1006                    None
1007                }
1008            })
1009            .collect();
1010
1011        // Collect negations from KB and context
1012        let negations: Vec<ProofExpr> = self
1013            .knowledge_base
1014            .iter()
1015            .chain(goal.context.iter())
1016            .filter_map(|expr| {
1017                if let ProofExpr::Not(inner) = expr {
1018                    Some((**inner).clone())
1019                } else {
1020                    None
1021                }
1022            })
1023            .collect();
1024
1025        // For each disjunction A ∨ B
1026        for (disj_expr, left, right) in &disjunctions {
1027            // Check if ¬left is in KB (so right must be true)
1028            for negated in &negations {
1029                if exprs_structurally_equal(negated, left) {
1030                    // We have A ∨ B and ¬A, so B is true
1031                    // Check if B matches our goal
1032                    if let Ok(subst) = unify_exprs(&goal.target, right) {
1033                        let disj_leaf = DerivationTree::leaf(
1034                            disj_expr.clone(),
1035                            InferenceRule::PremiseMatch,
1036                        );
1037                        let neg_leaf = DerivationTree::leaf(
1038                            ProofExpr::Not(Box::new(left.clone())),
1039                            InferenceRule::PremiseMatch,
1040                        );
1041                        return Ok(Some(
1042                            DerivationTree::new(
1043                                goal.target.clone(),
1044                                InferenceRule::DisjunctionElim,
1045                                vec![disj_leaf, neg_leaf],
1046                            )
1047                            .with_substitution(subst),
1048                        ));
1049                    }
1050                }
1051
1052                // Check if ¬right is in KB (so left must be true)
1053                if exprs_structurally_equal(negated, right) {
1054                    // We have A ∨ B and ¬B, so A is true
1055                    // Check if A matches our goal
1056                    if let Ok(subst) = unify_exprs(&goal.target, left) {
1057                        let disj_leaf = DerivationTree::leaf(
1058                            disj_expr.clone(),
1059                            InferenceRule::PremiseMatch,
1060                        );
1061                        let neg_leaf = DerivationTree::leaf(
1062                            ProofExpr::Not(Box::new(right.clone())),
1063                            InferenceRule::PremiseMatch,
1064                        );
1065                        return Ok(Some(
1066                            DerivationTree::new(
1067                                goal.target.clone(),
1068                                InferenceRule::DisjunctionElim,
1069                                vec![disj_leaf, neg_leaf],
1070                            )
1071                            .with_substitution(subst),
1072                        ));
1073                    }
1074                }
1075            }
1076        }
1077
1078        Ok(None)
1079    }
1080
1081    // =========================================================================
1082    // STRATEGY 5c: PROOF BY CONTRADICTION (REDUCTIO AD ABSURDUM)
1083    // =========================================================================
1084
1085    /// Try proof by contradiction: to prove ¬P, assume P and derive a contradiction.
1086    ///
1087    /// This implements reductio ad absurdum:
1088    /// 1. To prove ¬∃x P(x), assume ∃x P(x), derive contradiction, conclude ¬∃x P(x)
1089    /// 2. To prove ¬P, assume P, derive contradiction, conclude ¬P
1090    ///
1091    /// A contradiction is detected when both Q and ¬Q are derivable.
1092    fn try_reductio_ad_absurdum(
1093        &mut self,
1094        goal: &ProofGoal,
1095        depth: usize,
1096    ) -> ProofResult<Option<DerivationTree>> {
1097        // Only apply to negation goals
1098        let assumed = match &goal.target {
1099            ProofExpr::Not(inner) => (**inner).clone(),
1100            _ => return Ok(None),
1101        };
1102
1103        // Aggressive depth limit - reductio is expensive
1104        if depth > 5 {
1105            return Ok(None);
1106        }
1107
1108        // Special handling for existence negation goals: ¬∃x P(x)
1109        // This is crucial for paradoxes like the Barber Paradox
1110        if let ProofExpr::Exists { .. } = &assumed {
1111            return self.try_existence_negation_proof(&goal, &assumed, depth);
1112        }
1113
1114        // For non-existence goals, skip if they contain other quantifiers
1115        // (to avoid infinite loops with universal instantiation)
1116        if self.contains_quantifier(&assumed) {
1117            return Ok(None);
1118        }
1119
1120        // Create a temporary context with the assumption added
1121        let mut extended_context = goal.context.clone();
1122        extended_context.push(assumed.clone());
1123
1124        // Also Skolemize existentials from the assumption (but be careful)
1125        let skolemized = self.skolemize_existential(&assumed);
1126        for sk in &skolemized {
1127            extended_context.push(sk.clone());
1128        }
1129
1130        // Look for contradiction in the extended context + KB
1131        // Note: find_contradiction does NOT call prove_goal recursively
1132        if let Some(contradiction_proof) = self.find_contradiction(&extended_context, depth)? {
1133            // Found a contradiction! Build the reductio proof
1134            let assumption_leaf = DerivationTree::leaf(
1135                assumed.clone(),
1136                InferenceRule::PremiseMatch,
1137            );
1138
1139            return Ok(Some(DerivationTree::new(
1140                goal.target.clone(),
1141                InferenceRule::ReductioAdAbsurdum,
1142                vec![assumption_leaf, contradiction_proof],
1143            )));
1144        }
1145
1146        Ok(None)
1147    }
1148
1149    /// Try to prove ¬∃x P(x) by assuming ∃x P(x) and deriving contradiction.
1150    ///
1151    /// This is the core strategy for existence paradoxes like the Barber Paradox.
1152    /// Steps:
1153    /// 1. Assume ∃x P(x)
1154    /// 2. Skolemize to get P(c) for fresh constant c
1155    /// 3. Skolemize KB existentials (definite descriptions) to extract inner structure
1156    /// 4. Abstract event semantics to simple predicates
1157    /// 5. Instantiate universal premises with the Skolem constant
1158    /// 6. Extract uniqueness constraints and derive equalities
1159    /// 7. Look for contradiction (possibly via case analysis)
1160    fn try_existence_negation_proof(
1161        &mut self,
1162        goal: &ProofGoal,
1163        assumed_existence: &ProofExpr,
1164        depth: usize,
1165    ) -> ProofResult<Option<DerivationTree>> {
1166        // Skolemize the assumed existence: ∃x P(x) → P(c)
1167        let witness_facts = self.skolemize_existential(assumed_existence);
1168
1169        if witness_facts.is_empty() {
1170            return Ok(None);
1171        }
1172
1173        // Build extended context with witness facts
1174        let mut extended_context = goal.context.clone();
1175        extended_context.push(assumed_existence.clone());
1176
1177        // Add witness facts, abstracting events
1178        for fact in &witness_facts {
1179            let abstracted = self.abstract_all_events(fact);
1180            if !extended_context.contains(&abstracted) {
1181                extended_context.push(abstracted);
1182            }
1183            if !extended_context.contains(fact) {
1184                extended_context.push(fact.clone());
1185            }
1186        }
1187
1188        // Extract any Skolem constants from the witness facts
1189        let mut skolem_constants = self.extract_skolem_constants(&witness_facts);
1190
1191        // CRITICAL: Skolemize KB existentials to extract definite description structure.
1192        // Natural language "The barber" creates:
1193        // ∃y ((barber(y) ∧ ∀z (barber(z) → z = y)) ∧ ∀x ...)
1194        // We need to Skolemize these to access the inner universals.
1195        let kb_skolemized = self.skolemize_kb_existentials();
1196        for fact in &kb_skolemized {
1197            let abstracted = self.abstract_all_events(fact);
1198            if !extended_context.contains(&abstracted) {
1199                extended_context.push(abstracted);
1200            }
1201            if !extended_context.contains(fact) {
1202                extended_context.push(fact.clone());
1203            }
1204        }
1205
1206        // Extract additional Skolem constants from KB
1207        let kb_skolems = self.extract_skolem_constants(&kb_skolemized);
1208        for sk in kb_skolems {
1209            if !skolem_constants.contains(&sk) {
1210                skolem_constants.push(sk);
1211            }
1212        }
1213
1214        // Also extract unified definite description constants (e.g., "the_barber")
1215        // These are created by unify_definite_descriptions and should be treated like Skolems
1216        for expr in &self.knowledge_base {
1217            self.collect_unified_constants(expr, &mut skolem_constants);
1218        }
1219
1220        // Instantiate universal premises with Skolem constants
1221        let instantiated = self.instantiate_universals_with_constants(
1222            &extended_context,
1223            &skolem_constants,
1224        );
1225        for inst in &instantiated {
1226            let abstracted = self.abstract_all_events(inst);
1227            if !extended_context.contains(&abstracted) {
1228                extended_context.push(abstracted);
1229            }
1230        }
1231
1232        // Also process KB universals
1233        let kb_instantiated = self.instantiate_kb_universals_with_constants(&skolem_constants);
1234        for inst in &kb_instantiated {
1235            let abstracted = self.abstract_all_events(inst);
1236            if !extended_context.contains(&abstracted) {
1237                extended_context.push(abstracted);
1238            }
1239        }
1240
1241        // CRITICAL: Extract uniqueness constraints from definite descriptions
1242        // and derive equalities between Skolem constants and KB witnesses.
1243        // This handles Russell's definite descriptions: "The barber" creates
1244        // ∃y ((barber(y) ∧ ∀z (barber(z) → z = y)) ∧ ...)
1245        let derived_equalities = self.derive_equalities_from_uniqueness_constraints(
1246            &extended_context,
1247            &skolem_constants,
1248        );
1249
1250        // Add derived equalities to context
1251        for eq in &derived_equalities {
1252            if !extended_context.contains(eq) {
1253                extended_context.push(eq.clone());
1254            }
1255        }
1256
1257        // Apply derived equalities to substitute terms throughout context
1258        // This unifies facts about different barbers (sk_0, y, v) into a single entity
1259        let unified_context = self.apply_equalities_to_context(&extended_context, &derived_equalities);
1260
1261        // Look for direct contradiction first (in unified context)
1262        if let Some(contradiction_proof) = self.find_contradiction(&unified_context, depth)? {
1263            let assumption_leaf = DerivationTree::leaf(
1264                assumed_existence.clone(),
1265                InferenceRule::PremiseMatch,
1266            );
1267
1268            return Ok(Some(DerivationTree::new(
1269                goal.target.clone(),
1270                InferenceRule::ReductioAdAbsurdum,
1271                vec![assumption_leaf, contradiction_proof],
1272            )));
1273        }
1274
1275        // Try case analysis for self-referential structures (like Barber Paradox)
1276        if let Some(case_proof) = self.try_case_analysis_contradiction(&unified_context, &skolem_constants, depth)? {
1277            let assumption_leaf = DerivationTree::leaf(
1278                assumed_existence.clone(),
1279                InferenceRule::PremiseMatch,
1280            );
1281
1282            return Ok(Some(DerivationTree::new(
1283                goal.target.clone(),
1284                InferenceRule::ReductioAdAbsurdum,
1285                vec![assumption_leaf, case_proof],
1286            )));
1287        }
1288
1289        Ok(None)
1290    }
1291
1292    /// Skolemize all existential expressions in the KB.
1293    ///
1294    /// This is essential for definite descriptions from natural language.
1295    /// "The barber" creates `∃y ((barber(y) ∧ ∀z (barber(z) → z = y)) ∧ ...)`.
1296    /// We Skolemize to extract the inner structure.
1297    fn skolemize_kb_existentials(&mut self) -> Vec<ProofExpr> {
1298        let mut results = Vec::new();
1299
1300        for expr in &self.knowledge_base.clone() {
1301            if let ProofExpr::Exists { .. } = expr {
1302                let skolemized = self.skolemize_existential(expr);
1303                results.extend(skolemized);
1304            }
1305        }
1306
1307        results
1308    }
1309
1310    // =========================================================================
1311    // EQUATIONAL REASONING FOR DEFINITE DESCRIPTIONS
1312    // =========================================================================
1313
1314    /// Derive equalities from uniqueness constraints in definite descriptions.
1315    ///
1316    /// Given facts like `barber(sk_0)` and uniqueness constraints like
1317    /// `∀z (barber(z) → z = y)`, derive `sk_0 = y`.
1318    ///
1319    /// This is essential for Russell's definite descriptions where
1320    /// "The barber" creates `∃y ((barber(y) ∧ ∀z (barber(z) → z = y)) ∧ ...)`.
1321    fn derive_equalities_from_uniqueness_constraints(
1322        &self,
1323        context: &[ProofExpr],
1324        skolem_constants: &[String],
1325    ) -> Vec<ProofExpr> {
1326        let mut equalities = Vec::new();
1327
1328        // Collect all uniqueness constraints from KB and context
1329        // Pattern: ∀z (P(z) → z = c) where c is a constant/variable
1330        let uniqueness_constraints = self.extract_uniqueness_constraints(context);
1331
1332        // For each Skolem constant, check if it satisfies predicates
1333        // with uniqueness constraints
1334        for skolem in skolem_constants {
1335            for (predicate_name, unique_entity) in &uniqueness_constraints {
1336                // Check if we have predicate(skolem) in context
1337                let skolem_term = ProofTerm::Constant(skolem.clone());
1338                let skolem_satisfies_predicate = context.iter().any(|expr| {
1339                    self.predicate_matches(expr, predicate_name, &skolem_term)
1340                });
1341
1342                if skolem_satisfies_predicate {
1343                    // Derive: skolem = unique_entity
1344                    let equality = ProofExpr::Identity(
1345                        skolem_term.clone(),
1346                        unique_entity.clone(),
1347                    );
1348                    if !equalities.contains(&equality) {
1349                        equalities.push(equality);
1350                    }
1351
1352                    // Also add the symmetric version for easier matching
1353                    let sym_equality = ProofExpr::Identity(
1354                        unique_entity.clone(),
1355                        skolem_term.clone(),
1356                    );
1357                    if !equalities.contains(&sym_equality) {
1358                        equalities.push(sym_equality);
1359                    }
1360                }
1361            }
1362        }
1363
1364        // Derive transitive equalities: if sk_0 = y and sk_0 = v, then y = v
1365        let mut transitive_equalities = Vec::new();
1366        for eq1 in &equalities {
1367            if let ProofExpr::Identity(t1, t2) = eq1 {
1368                for eq2 in &equalities {
1369                    if let ProofExpr::Identity(t3, t4) = eq2 {
1370                        // If t1 = t2 and t1 = t4, then t2 = t4
1371                        if t1 == t3 && t2 != t4 {
1372                            let trans_eq = ProofExpr::Identity(t2.clone(), t4.clone());
1373                            if !equalities.contains(&trans_eq) && !transitive_equalities.contains(&trans_eq) {
1374                                transitive_equalities.push(trans_eq);
1375                            }
1376                        }
1377                        // If t1 = t2 and t3 = t1, then t2 = t3
1378                        if t1 == t4 && t2 != t3 {
1379                            let trans_eq = ProofExpr::Identity(t2.clone(), t3.clone());
1380                            if !equalities.contains(&trans_eq) && !transitive_equalities.contains(&trans_eq) {
1381                                transitive_equalities.push(trans_eq);
1382                            }
1383                        }
1384                    }
1385                }
1386            }
1387        }
1388        equalities.extend(transitive_equalities);
1389
1390        equalities
1391    }
1392
1393    /// Extract uniqueness constraints from context and KB.
1394    ///
1395    /// Looks for patterns like `∀z (P(z) → z = c)` which establish
1396    /// that c is the unique entity satisfying P.
1397    fn extract_uniqueness_constraints(&self, context: &[ProofExpr]) -> Vec<(String, ProofTerm)> {
1398        let mut constraints = Vec::new();
1399
1400        for expr in context.iter().chain(self.knowledge_base.iter()) {
1401            self.extract_uniqueness_from_expr(expr, &mut constraints);
1402        }
1403
1404        constraints
1405    }
1406
1407    /// Recursively extract uniqueness constraints from an expression.
1408    fn extract_uniqueness_from_expr(&self, expr: &ProofExpr, constraints: &mut Vec<(String, ProofTerm)>) {
1409        match expr {
1410            // Direct uniqueness pattern: ∀z (P(z) → z = c)
1411            ProofExpr::ForAll { variable, body } => {
1412                if let ProofExpr::Implies(ante, cons) = body.as_ref() {
1413                    if let ProofExpr::Identity(left, right) = cons.as_ref() {
1414                        // Check if it's "z = c" where z is the quantified variable
1415                        let var_term = ProofTerm::Variable(variable.clone());
1416                        if left == &var_term {
1417                            // Extract the predicate name from the antecedent
1418                            if let Some(pred_name) = self.extract_unary_predicate_name(ante, variable) {
1419                                // right is the unique entity
1420                                constraints.push((pred_name, right.clone()));
1421                            }
1422                        } else if right == &var_term {
1423                            // Check c = z form
1424                            if let Some(pred_name) = self.extract_unary_predicate_name(ante, variable) {
1425                                constraints.push((pred_name, left.clone()));
1426                            }
1427                        }
1428                    }
1429                }
1430                // Recurse into body for nested structures
1431                self.extract_uniqueness_from_expr(body, constraints);
1432            }
1433
1434            // Conjunction: extract from both sides
1435            ProofExpr::And(left, right) => {
1436                self.extract_uniqueness_from_expr(left, constraints);
1437                self.extract_uniqueness_from_expr(right, constraints);
1438            }
1439
1440            // Existential: extract from body (definite descriptions are wrapped in ∃)
1441            ProofExpr::Exists { body, .. } => {
1442                self.extract_uniqueness_from_expr(body, constraints);
1443            }
1444
1445            _ => {}
1446        }
1447    }
1448
1449    /// Extract the predicate name from a unary predicate application.
1450    ///
1451    /// Given P(z) where z is the variable, returns "P".
1452    fn extract_unary_predicate_name(&self, expr: &ProofExpr, var: &str) -> Option<String> {
1453        match expr {
1454            ProofExpr::Predicate { name, args, .. } => {
1455                if args.len() == 1 {
1456                    if let ProofTerm::Variable(v) = &args[0] {
1457                        if v == var {
1458                            return Some(name.clone());
1459                        }
1460                    }
1461                }
1462                None
1463            }
1464            _ => None,
1465        }
1466    }
1467
1468    /// Check if an expression is a predicate with the given name applied to the term.
1469    fn predicate_matches(&self, expr: &ProofExpr, pred_name: &str, term: &ProofTerm) -> bool {
1470        match expr {
1471            ProofExpr::Predicate { name, args, .. } => {
1472                name == pred_name && args.len() == 1 && &args[0] == term
1473            }
1474            _ => false,
1475        }
1476    }
1477
1478    /// Apply derived equalities to substitute terms throughout context.
1479    ///
1480    /// This unifies facts about different entities (sk_0, y, v) by replacing
1481    /// all occurrences with a canonical representative (the first Skolem constant).
1482    fn apply_equalities_to_context(
1483        &self,
1484        context: &[ProofExpr],
1485        equalities: &[ProofExpr],
1486    ) -> Vec<ProofExpr> {
1487        if equalities.is_empty() {
1488            return context.to_vec();
1489        }
1490
1491        // Build a substitution map from equalities
1492        // Use the first term as the canonical representative
1493        let mut substitutions: Vec<(&ProofTerm, &ProofTerm)> = Vec::new();
1494        for eq in equalities {
1495            if let ProofExpr::Identity(t1, t2) = eq {
1496                // Prefer Skolem constants as canonical (they're from our assumption)
1497                if let ProofTerm::Constant(c) = t1 {
1498                    if c.starts_with("sk_") {
1499                        substitutions.push((t2, t1)); // t2 → t1 (Skolem)
1500                        continue;
1501                    }
1502                }
1503                if let ProofTerm::Constant(c) = t2 {
1504                    if c.starts_with("sk_") {
1505                        substitutions.push((t1, t2)); // t1 → t2 (Skolem)
1506                        continue;
1507                    }
1508                }
1509                // Default: first term is canonical
1510                substitutions.push((t2, t1));
1511            }
1512        }
1513
1514        // Apply substitutions to each expression in context
1515        let mut unified_context = Vec::new();
1516        for expr in context {
1517            let mut unified = expr.clone();
1518            for (from, to) in &substitutions {
1519                unified = self.substitute_term_in_expr(&unified, from, to);
1520            }
1521            // Add abstracted version too
1522            let abstracted = self.abstract_all_events(&unified);
1523            if !unified_context.contains(&unified) {
1524                unified_context.push(unified);
1525            }
1526            if !unified_context.contains(&abstracted) {
1527                unified_context.push(abstracted);
1528            }
1529        }
1530
1531        // Also add implications with substituted terms
1532        // This ensures cyclic implications like P(sk,sk) → ¬P(sk,sk) are in context
1533        for expr in context {
1534            if let ProofExpr::ForAll { variable, body } = expr {
1535                if let ProofExpr::Implies(_, _) = body.as_ref() {
1536                    // Find any Skolem constants and instantiate
1537                    for (from, to) in &substitutions {
1538                        if let ProofTerm::Constant(c) = to {
1539                            if c.starts_with("sk_") {
1540                                // Instantiate this universal with the Skolem constant
1541                                let mut subst = Substitution::new();
1542                                subst.insert(variable.clone(), (*to).clone());
1543                                let instantiated = apply_subst_to_expr(body, &subst);
1544                                let abstracted = self.abstract_all_events(&instantiated);
1545                                if !unified_context.contains(&abstracted) {
1546                                    unified_context.push(abstracted);
1547                                }
1548                            }
1549                        }
1550                    }
1551                }
1552            }
1553        }
1554
1555        unified_context
1556    }
1557
1558    /// Extract Skolem constants from a list of expressions.
1559    fn extract_skolem_constants(&self, exprs: &[ProofExpr]) -> Vec<String> {
1560        let mut constants = Vec::new();
1561        for expr in exprs {
1562            self.collect_skolem_constants_from_expr(expr, &mut constants);
1563        }
1564        constants.sort();
1565        constants.dedup();
1566        constants
1567    }
1568
1569    /// Helper to collect Skolem constants from an expression.
1570    fn collect_skolem_constants_from_expr(&self, expr: &ProofExpr, constants: &mut Vec<String>) {
1571        match expr {
1572            ProofExpr::Predicate { args, .. } => {
1573                for arg in args {
1574                    self.collect_skolem_constants_from_term(arg, constants);
1575                }
1576            }
1577            ProofExpr::And(l, r) | ProofExpr::Or(l, r) | ProofExpr::Implies(l, r) | ProofExpr::Iff(l, r) => {
1578                self.collect_skolem_constants_from_expr(l, constants);
1579                self.collect_skolem_constants_from_expr(r, constants);
1580            }
1581            ProofExpr::Not(inner) => {
1582                self.collect_skolem_constants_from_expr(inner, constants);
1583            }
1584            ProofExpr::Identity(l, r) => {
1585                self.collect_skolem_constants_from_term(l, constants);
1586                self.collect_skolem_constants_from_term(r, constants);
1587            }
1588            ProofExpr::NeoEvent { roles, .. } => {
1589                for (_, term) in roles {
1590                    self.collect_skolem_constants_from_term(term, constants);
1591                }
1592            }
1593            _ => {}
1594        }
1595    }
1596
1597    /// Helper to collect Skolem constants from a term.
1598    /// Collect unified definite description constants (e.g., "the_barber")
1599    /// These are created by unify_definite_descriptions and start with "the_".
1600    fn collect_unified_constants(&self, expr: &ProofExpr, constants: &mut Vec<String>) {
1601        match expr {
1602            ProofExpr::Predicate { args, .. } => {
1603                for arg in args {
1604                    if let ProofTerm::Constant(name) = arg {
1605                        if name.starts_with("the_") && !constants.contains(name) {
1606                            constants.push(name.clone());
1607                        }
1608                    }
1609                }
1610            }
1611            ProofExpr::And(left, right) | ProofExpr::Or(left, right) |
1612            ProofExpr::Implies(left, right) | ProofExpr::Iff(left, right) => {
1613                self.collect_unified_constants(left, constants);
1614                self.collect_unified_constants(right, constants);
1615            }
1616            ProofExpr::Not(inner) => self.collect_unified_constants(inner, constants),
1617            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
1618                self.collect_unified_constants(body, constants);
1619            }
1620            ProofExpr::Identity(t1, t2) => {
1621                if let ProofTerm::Constant(name) = t1 {
1622                    if name.starts_with("the_") && !constants.contains(name) {
1623                        constants.push(name.clone());
1624                    }
1625                }
1626                if let ProofTerm::Constant(name) = t2 {
1627                    if name.starts_with("the_") && !constants.contains(name) {
1628                        constants.push(name.clone());
1629                    }
1630                }
1631            }
1632            _ => {}
1633        }
1634    }
1635
1636    fn collect_skolem_constants_from_term(&self, term: &ProofTerm, constants: &mut Vec<String>) {
1637        match term {
1638            ProofTerm::Constant(name) if name.starts_with("sk_") => {
1639                constants.push(name.clone());
1640            }
1641            ProofTerm::Function(_, args) => {
1642                for arg in args {
1643                    self.collect_skolem_constants_from_term(arg, constants);
1644                }
1645            }
1646            ProofTerm::Group(terms) => {
1647                for t in terms {
1648                    self.collect_skolem_constants_from_term(t, constants);
1649                }
1650            }
1651            _ => {}
1652        }
1653    }
1654
1655    /// Instantiate universal quantifiers in the context with given constants.
1656    fn instantiate_universals_with_constants(
1657        &self,
1658        context: &[ProofExpr],
1659        constants: &[String],
1660    ) -> Vec<ProofExpr> {
1661        let mut results = Vec::new();
1662
1663        for expr in context {
1664            if let ProofExpr::ForAll { variable, body } = expr {
1665                for constant in constants {
1666                    let mut subst = Substitution::new();
1667                    subst.insert(variable.clone(), ProofTerm::Constant(constant.clone()));
1668                    let instantiated = apply_subst_to_expr(body, &subst);
1669                    results.push(instantiated);
1670                }
1671            }
1672        }
1673
1674        results
1675    }
1676
1677    /// Instantiate universal quantifiers in KB with given constants.
1678    fn instantiate_kb_universals_with_constants(&self, constants: &[String]) -> Vec<ProofExpr> {
1679        let mut results = Vec::new();
1680
1681        for expr in &self.knowledge_base {
1682            if let ProofExpr::ForAll { variable, body } = expr {
1683                for constant in constants {
1684                    let mut subst = Substitution::new();
1685                    subst.insert(variable.clone(), ProofTerm::Constant(constant.clone()));
1686                    let instantiated = apply_subst_to_expr(body, &subst);
1687                    results.push(instantiated);
1688                }
1689            }
1690        }
1691
1692        results
1693    }
1694
1695    // =========================================================================
1696    // CASE ANALYSIS (TERTIUM NON DATUR)
1697    // =========================================================================
1698
1699    /// Try case analysis to derive a contradiction.
1700    ///
1701    /// For self-referential structures like the Barber Paradox:
1702    /// - Split on a predicate P(c, c) where c is a Skolem constant
1703    /// - Case 1: Assume P(c, c), derive ¬P(c, c) → contradiction
1704    /// - Case 2: Assume ¬P(c, c), derive P(c, c) → contradiction
1705    /// Either way we get contradiction (law of excluded middle).
1706    fn try_case_analysis_contradiction(
1707        &mut self,
1708        context: &[ProofExpr],
1709        skolem_constants: &[String],
1710        depth: usize,
1711    ) -> ProofResult<Option<DerivationTree>> {
1712        // Find candidate predicates for case splitting
1713        // Look for self-referential predicates: P(c, c) where c is a Skolem constant
1714        let candidates = self.find_case_split_candidates(context, skolem_constants);
1715
1716        for candidate in candidates {
1717            // Case 1: Assume the candidate is true
1718            let mut context_with_pos = context.to_vec();
1719            if !context_with_pos.contains(&candidate) {
1720                context_with_pos.push(candidate.clone());
1721            }
1722
1723            // Case 2: Assume the candidate is false
1724            let negated = ProofExpr::Not(Box::new(candidate.clone()));
1725            let mut context_with_neg = context.to_vec();
1726            if !context_with_neg.contains(&negated) {
1727                context_with_neg.push(negated.clone());
1728            }
1729
1730            // Try to derive contradiction in both cases
1731            let case1_contradiction = self.find_contradiction(&context_with_pos, depth)?;
1732            let case2_contradiction = self.find_contradiction(&context_with_neg, depth)?;
1733
1734            // If both cases lead to contradiction, we have a proof
1735            if let (Some(case1_proof), Some(case2_proof)) = (case1_contradiction, case2_contradiction) {
1736                // Build the case analysis proof tree
1737                let case1_tree = DerivationTree::new(
1738                    ProofExpr::Atom("⊥".into()),
1739                    InferenceRule::PremiseMatch,
1740                    vec![case1_proof],
1741                );
1742                let case2_tree = DerivationTree::new(
1743                    ProofExpr::Atom("⊥".into()),
1744                    InferenceRule::PremiseMatch,
1745                    vec![case2_proof],
1746                );
1747
1748                return Ok(Some(DerivationTree::new(
1749                    ProofExpr::Atom("⊥".into()),
1750                    InferenceRule::CaseAnalysis {
1751                        case_formula: format!("{}", candidate),
1752                    },
1753                    vec![case1_tree, case2_tree],
1754                )));
1755            }
1756        }
1757
1758        Ok(None)
1759    }
1760
1761    /// Find candidate predicates for case splitting.
1762    ///
1763    /// Looks for:
1764    /// 1. Self-referential predicates: P(c, c) where c is a Skolem constant
1765    /// 2. Predicates that appear in contradictory implications: P → ¬P and ¬P → P
1766    fn find_case_split_candidates(
1767        &self,
1768        context: &[ProofExpr],
1769        skolem_constants: &[String],
1770    ) -> Vec<ProofExpr> {
1771        let mut candidates = Vec::new();
1772
1773        // Strategy 1: Find self-referential predicates P(c, c)
1774        for expr in context {
1775            if let ProofExpr::Predicate { name, args, world } = expr {
1776                // Check if it's a binary predicate with the same Skolem constant twice
1777                if args.len() == 2 {
1778                    if let (ProofTerm::Constant(c1), ProofTerm::Constant(c2)) = (&args[0], &args[1]) {
1779                        if c1 == c2 && skolem_constants.contains(c1) {
1780                            candidates.push(expr.clone());
1781                        }
1782                    }
1783                }
1784            }
1785        }
1786
1787        // Strategy 2: Find predicates involved in cyclic implications
1788        // Look for patterns like: (P → ¬P) ∧ (¬P → P)
1789        let implications: Vec<(ProofExpr, ProofExpr)> = context.iter()
1790            .chain(self.knowledge_base.iter())
1791            .filter_map(|e| {
1792                if let ProofExpr::Implies(ante, cons) = e {
1793                    Some(((**ante).clone(), (**cons).clone()))
1794                } else {
1795                    None
1796                }
1797            })
1798            .collect();
1799
1800        for (ante, cons) in &implications {
1801            // Check for P → ¬P pattern
1802            if let ProofExpr::Not(inner) = cons {
1803                if exprs_structurally_equal(ante, inner) {
1804                    // Found P → ¬P, check if ¬P → P also exists
1805                    let neg_ante = ProofExpr::Not(Box::new(ante.clone()));
1806                    for (a2, c2) in &implications {
1807                        if exprs_structurally_equal(a2, &neg_ante) && exprs_structurally_equal(c2, ante) {
1808                            // Found the cyclic pair - ante is a good candidate
1809                            if !candidates.contains(ante) {
1810                                candidates.push(ante.clone());
1811                            }
1812                        }
1813                    }
1814                }
1815            }
1816        }
1817
1818        // Strategy 3: Generate self-referential predicates for Skolem constants
1819        // For each Skolem constant sk_N, look for predicates P and create P(sk_N, sk_N)
1820        for const_name in skolem_constants {
1821            // Look for action predicates in implications
1822            for expr in context.iter().chain(self.knowledge_base.iter()) {
1823                if let ProofExpr::Implies(ante, cons) = expr {
1824                    // Extract predicate names from consequences
1825                    self.extract_predicate_template(cons, const_name, &mut candidates);
1826                }
1827            }
1828        }
1829
1830        candidates
1831    }
1832
1833    /// Extract a predicate template and instantiate with a Skolem constant.
1834    fn extract_predicate_template(
1835        &self,
1836        expr: &ProofExpr,
1837        skolem: &str,
1838        candidates: &mut Vec<ProofExpr>,
1839    ) {
1840        match expr {
1841            ProofExpr::Predicate { name, args, world } if args.len() == 2 => {
1842                // Create a self-referential version: P(sk, sk)
1843                let self_ref = ProofExpr::Predicate {
1844                    name: name.clone(),
1845                    args: vec![
1846                        ProofTerm::Constant(skolem.to_string()),
1847                        ProofTerm::Constant(skolem.to_string()),
1848                    ],
1849                    world: world.clone(),
1850                };
1851                if !candidates.contains(&self_ref) {
1852                    candidates.push(self_ref);
1853                }
1854            }
1855            ProofExpr::Not(inner) => {
1856                self.extract_predicate_template(inner, skolem, candidates);
1857            }
1858            ProofExpr::NeoEvent { verb, .. } => {
1859                // Create abstracted predicate version
1860                let self_ref = ProofExpr::Predicate {
1861                    name: verb.to_lowercase(),
1862                    args: vec![
1863                        ProofTerm::Constant(skolem.to_string()),
1864                        ProofTerm::Constant(skolem.to_string()),
1865                    ],
1866                    world: None,
1867                };
1868                if !candidates.contains(&self_ref) {
1869                    candidates.push(self_ref);
1870                }
1871            }
1872            _ => {}
1873        }
1874    }
1875
1876    // =========================================================================
1877    // STRATEGY 5d: EXISTENTIAL ELIMINATION
1878    // =========================================================================
1879
1880    /// Try to eliminate existential quantifiers from premises.
1881    ///
1882    /// For each ∃x P(x) in the KB or context:
1883    /// 1. Generate a fresh Skolem constant c
1884    /// 2. Add P(c) to the context
1885    /// 3. Abstract any event semantics to simple predicates
1886    /// 4. Try to prove the goal with the extended context
1887    fn try_existential_elimination(
1888        &mut self,
1889        goal: &ProofGoal,
1890        depth: usize,
1891    ) -> ProofResult<Option<DerivationTree>> {
1892        // Depth guard to prevent infinite loops
1893        if depth > 8 {
1894            return Ok(None);
1895        }
1896
1897        // Find existential expressions in KB and context
1898        let existentials: Vec<ProofExpr> = self.knowledge_base.iter()
1899            .chain(goal.context.iter())
1900            .filter(|e| matches!(e, ProofExpr::Exists { .. }))
1901            .cloned()
1902            .collect();
1903
1904        if existentials.is_empty() {
1905            return Ok(None);
1906        }
1907
1908        // Try eliminating each existential
1909        for exist_expr in existentials {
1910            // Skolemize to get witness facts
1911            let witness_facts = self.skolemize_existential(&exist_expr);
1912
1913            if witness_facts.is_empty() {
1914                continue;
1915            }
1916
1917            // Abstract event semantics in witness facts
1918            let abstracted_facts: Vec<ProofExpr> = witness_facts.iter()
1919                .map(|f| self.abstract_all_events(f))
1920                .collect();
1921
1922            // Build extended context with witness facts
1923            let mut extended_context = goal.context.clone();
1924            for fact in &abstracted_facts {
1925                if !extended_context.contains(fact) {
1926                    extended_context.push(fact.clone());
1927                }
1928            }
1929
1930            // Also add the original witness facts (in case abstraction changes things)
1931            for fact in &witness_facts {
1932                if !extended_context.contains(fact) {
1933                    extended_context.push(fact.clone());
1934                }
1935            }
1936
1937            // Try to prove the goal with the extended context
1938            let extended_goal = ProofGoal::with_context(goal.target.clone(), extended_context);
1939
1940            // Use a fresh engine to avoid polluting our KB
1941            // But we need to be careful about depth to prevent loops
1942            if let Ok(inner_proof) = self.prove_goal(extended_goal, depth + 1) {
1943                // Build proof tree with existential elimination
1944                let witness_name = if let ProofExpr::Exists { variable, .. } = &exist_expr {
1945                    variable.clone()
1946                } else {
1947                    "witness".to_string()
1948                };
1949
1950                return Ok(Some(DerivationTree::new(
1951                    goal.target.clone(),
1952                    InferenceRule::ExistentialElim { witness: witness_name },
1953                    vec![inner_proof],
1954                )));
1955            }
1956        }
1957
1958        Ok(None)
1959    }
1960
1961    /// Check if an expression contains quantifiers.
1962    fn contains_quantifier(&self, expr: &ProofExpr) -> bool {
1963        match expr {
1964            ProofExpr::ForAll { .. } | ProofExpr::Exists { .. } => true,
1965            ProofExpr::And(l, r)
1966            | ProofExpr::Or(l, r)
1967            | ProofExpr::Implies(l, r)
1968            | ProofExpr::Iff(l, r) => self.contains_quantifier(l) || self.contains_quantifier(r),
1969            ProofExpr::Not(inner) => self.contains_quantifier(inner),
1970            _ => false,
1971        }
1972    }
1973
1974    /// Skolemize an existential expression.
1975    ///
1976    /// Given ∃x P(x), introduce a fresh Skolem constant c and return P(c).
1977    /// For nested structures like ∃x((type(x) ∧ unique(x)) ∧ prop(x)),
1978    /// we extract the predicates with the Skolem constant.
1979    fn skolemize_existential(&mut self, expr: &ProofExpr) -> Vec<ProofExpr> {
1980        let mut results = Vec::new();
1981
1982        if let ProofExpr::Exists { variable, body } = expr {
1983            // Generate a fresh Skolem constant
1984            let skolem = format!("sk_{}", self.fresh_var());
1985
1986            // Apply substitution to the body
1987            let mut subst = Substitution::new();
1988            subst.insert(variable.clone(), ProofTerm::Constant(skolem.clone()));
1989
1990            let instantiated = apply_subst_to_expr(body, &subst);
1991
1992            // Flatten conjunctions into separate facts
1993            self.flatten_conjunction(&instantiated, &mut results);
1994
1995            // Handle nested existentials in the result
1996            let mut i = 0;
1997            while i < results.len() {
1998                if let ProofExpr::Exists { .. } = &results[i] {
1999                    let nested = results.remove(i);
2000                    let nested_skolem = self.skolemize_existential(&nested);
2001                    results.extend(nested_skolem);
2002                } else {
2003                    i += 1;
2004                }
2005            }
2006        }
2007
2008        results
2009    }
2010
2011    /// Flatten a conjunction into a list of its components.
2012    fn flatten_conjunction(&self, expr: &ProofExpr, results: &mut Vec<ProofExpr>) {
2013        match expr {
2014            ProofExpr::And(left, right) => {
2015                self.flatten_conjunction(left, results);
2016                self.flatten_conjunction(right, results);
2017            }
2018            other => results.push(other.clone()),
2019        }
2020    }
2021
2022    // =========================================================================
2023    // DEFINITE DESCRIPTION SIMPLIFICATION
2024    // =========================================================================
2025
2026    /// Check if a predicate is a tautological identity check: name(name)
2027    /// This occurs when parsing "the butler" creates butler(butler)
2028    fn is_tautological_identity(&self, expr: &ProofExpr) -> bool {
2029        if let ProofExpr::Predicate { name, args, .. } = expr {
2030            args.len() == 1 && matches!(
2031                &args[0],
2032                ProofTerm::Constant(c) | ProofTerm::BoundVarRef(c) | ProofTerm::Variable(c) if c == name
2033            )
2034        } else {
2035            false
2036        }
2037    }
2038
2039    /// Simplify conjunction by removing tautological identity predicates.
2040    /// (butler(butler) ∧ P) → P when butler is a constant
2041    fn simplify_definite_description_conjunction(&self, expr: &ProofExpr) -> ProofExpr {
2042        match expr {
2043            ProofExpr::And(left, right) => {
2044                // First simplify children
2045                let left_simplified = self.simplify_definite_description_conjunction(left);
2046                let right_simplified = self.simplify_definite_description_conjunction(right);
2047
2048                // Remove tautological identities from the conjunction
2049                if self.is_tautological_identity(&left_simplified) {
2050                    return right_simplified;
2051                }
2052                if self.is_tautological_identity(&right_simplified) {
2053                    return left_simplified;
2054                }
2055
2056                ProofExpr::And(
2057                    Box::new(left_simplified),
2058                    Box::new(right_simplified),
2059                )
2060            }
2061            ProofExpr::Or(left, right) => ProofExpr::Or(
2062                Box::new(self.simplify_definite_description_conjunction(left)),
2063                Box::new(self.simplify_definite_description_conjunction(right)),
2064            ),
2065            ProofExpr::Implies(left, right) => ProofExpr::Implies(
2066                Box::new(self.simplify_definite_description_conjunction(left)),
2067                Box::new(self.simplify_definite_description_conjunction(right)),
2068            ),
2069            ProofExpr::Iff(left, right) => ProofExpr::Iff(
2070                Box::new(self.simplify_definite_description_conjunction(left)),
2071                Box::new(self.simplify_definite_description_conjunction(right)),
2072            ),
2073            ProofExpr::Not(inner) => ProofExpr::Not(
2074                Box::new(self.simplify_definite_description_conjunction(inner)),
2075            ),
2076            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
2077                variable: variable.clone(),
2078                body: Box::new(self.simplify_definite_description_conjunction(body)),
2079            },
2080            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
2081                variable: variable.clone(),
2082                body: Box::new(self.simplify_definite_description_conjunction(body)),
2083            },
2084            ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
2085                operator: operator.clone(),
2086                body: Box::new(self.simplify_definite_description_conjunction(body)),
2087            },
2088            _ => expr.clone(),
2089        }
2090    }
2091
2092    // =========================================================================
2093    // EVENT SEMANTICS ABSTRACTION
2094    // =========================================================================
2095
2096    /// Abstract Neo-Davidsonian event semantics to simple predicates.
2097    ///
2098    /// Converts: ∃e(Shave(e) ∧ Agent(e, x) ∧ Theme(e, y)) → shaves(x, y)
2099    ///
2100    /// This allows the proof engine to reason about events using simpler
2101    /// predicate logic, which is essential for paradoxes like the Barber Paradox.
2102    fn abstract_event_to_predicate(&self, expr: &ProofExpr) -> Option<ProofExpr> {
2103        match expr {
2104            // Direct NeoEvent abstraction
2105            ProofExpr::NeoEvent { verb, roles, .. } => {
2106                // Extract Agent and Theme/Patient roles
2107                let agent = roles.iter()
2108                    .find(|(role, _)| role == "Agent")
2109                    .map(|(_, term)| term.clone());
2110
2111                let theme = roles.iter()
2112                    .find(|(role, _)| role == "Theme" || role == "Patient")
2113                    .map(|(_, term)| term.clone());
2114
2115                // Build a simple predicate: verb(agent, theme) or verb(agent)
2116                let mut args = Vec::new();
2117                if let Some(a) = agent {
2118                    args.push(a);
2119                }
2120                if let Some(t) = theme {
2121                    args.push(t);
2122                }
2123
2124                // Lowercase the verb for predicate naming convention
2125                let pred_name = verb.to_lowercase();
2126
2127                Some(ProofExpr::Predicate {
2128                    name: pred_name,
2129                    args,
2130                    world: None,
2131                })
2132            }
2133
2134            // Handle Exists wrapping an event expression
2135            ProofExpr::Exists { variable, body } => {
2136                // Check if this is an event quantification
2137                if !self.is_event_variable(variable) {
2138                    return None;
2139                }
2140
2141                // Try direct NeoEvent abstraction
2142                if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2143                    return Some(abstracted);
2144                }
2145
2146                // Try to parse conjunction of event predicates
2147                // Pattern: ∃e(Verb(e) ∧ Agent(e, x) ∧ Theme(e, y)) → verb(x, y)
2148                if let Some(abstracted) = self.abstract_event_conjunction(variable, body) {
2149                    return Some(abstracted);
2150                }
2151
2152                None
2153            }
2154
2155            _ => None,
2156        }
2157    }
2158
2159    /// Abstract a conjunction of event predicates to a simple predicate.
2160    ///
2161    /// Handles: Verb(e) ∧ Agent(e, x) ∧ Theme(e, y) → verb(x, y)
2162    fn abstract_event_conjunction(&self, event_var: &str, body: &ProofExpr) -> Option<ProofExpr> {
2163        // Flatten the conjunction to get all components
2164        let mut components = Vec::new();
2165        self.flatten_conjunction(body, &mut components);
2166
2167        // Find verb predicate (single arg that matches event_var)
2168        let mut verb_name: Option<String> = None;
2169        let mut agent: Option<ProofTerm> = None;
2170        let mut theme: Option<ProofTerm> = None;
2171
2172        for comp in &components {
2173            if let ProofExpr::Predicate { name, args, .. } = comp {
2174                // Check if first arg is the event variable
2175                let first_is_event = args.first().map_or(false, |arg| {
2176                    matches!(arg, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == event_var)
2177                });
2178
2179                if !first_is_event && args.len() == 1 {
2180                    // Single arg predicate that's the event var
2181                    if let Some(ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v)) = args.first() {
2182                        if v == event_var {
2183                            verb_name = Some(name.clone());
2184                            continue;
2185                        }
2186                    }
2187                }
2188
2189                if first_is_event {
2190                    match name.as_str() {
2191                        "Agent" if args.len() == 2 => {
2192                            agent = Some(args[1].clone());
2193                        }
2194                        "Theme" | "Patient" if args.len() == 2 => {
2195                            theme = Some(args[1].clone());
2196                        }
2197                        _ if args.len() == 1 && verb_name.is_none() => {
2198                            // This is probably the verb predicate: Verb(e)
2199                            verb_name = Some(name.clone());
2200                        }
2201                        _ => {}
2202                    }
2203                }
2204            }
2205        }
2206
2207        // If we found a verb, construct the simple predicate
2208        if let Some(verb) = verb_name {
2209            let mut args = Vec::new();
2210            if let Some(a) = agent {
2211                args.push(a);
2212            }
2213            if let Some(t) = theme {
2214                args.push(t);
2215            }
2216
2217            return Some(ProofExpr::Predicate {
2218                name: verb.to_lowercase(),
2219                args,
2220                world: None,
2221            });
2222        }
2223
2224        None
2225    }
2226
2227    /// Check if a variable name looks like an event variable.
2228    ///
2229    /// Event variables are typically named "e", "e1", "e2", etc.
2230    fn is_event_variable(&self, var: &str) -> bool {
2231        var == "e" || var.starts_with("e_") ||
2232        (var.starts_with('e') && var.len() == 2 && var.chars().nth(1).map_or(false, |c| c.is_ascii_digit()))
2233    }
2234
2235    /// Recursively abstract all events in an expression.
2236    ///
2237    /// This transforms the entire expression tree, replacing event semantics
2238    /// with simple predicates wherever possible.
2239    fn abstract_all_events(&self, expr: &ProofExpr) -> ProofExpr {
2240        // First try direct abstraction
2241        if let Some(abstracted) = self.abstract_event_to_predicate(expr) {
2242            return abstracted;
2243        }
2244
2245        // Otherwise recurse into the structure
2246        match expr {
2247            ProofExpr::And(left, right) => ProofExpr::And(
2248                Box::new(self.abstract_all_events(left)),
2249                Box::new(self.abstract_all_events(right)),
2250            ),
2251            ProofExpr::Or(left, right) => ProofExpr::Or(
2252                Box::new(self.abstract_all_events(left)),
2253                Box::new(self.abstract_all_events(right)),
2254            ),
2255            ProofExpr::Implies(left, right) => ProofExpr::Implies(
2256                Box::new(self.abstract_all_events(left)),
2257                Box::new(self.abstract_all_events(right)),
2258            ),
2259            ProofExpr::Iff(left, right) => ProofExpr::Iff(
2260                Box::new(self.abstract_all_events(left)),
2261                Box::new(self.abstract_all_events(right)),
2262            ),
2263            ProofExpr::Not(inner) => {
2264                // Apply De Morgan for quantifiers: ¬∃x.P ≡ ∀x.¬P
2265                // This normalization is crucial for efficient proof search
2266                // (Converting negated existentials to universals helps the prover)
2267                if let ProofExpr::Exists { variable, body } = inner.as_ref() {
2268                    return ProofExpr::ForAll {
2269                        variable: variable.clone(),
2270                        body: Box::new(self.abstract_all_events(&ProofExpr::Not(body.clone()))),
2271                    };
2272                }
2273                // Note: We do NOT convert ¬∀x.P to ∃x.¬P because the prover
2274                // works better with universal quantifiers for backward chaining.
2275                ProofExpr::Not(Box::new(self.abstract_all_events(inner)))
2276            }
2277            ProofExpr::ForAll { variable, body } => {
2278                // Check for pattern: ∀x ¬(P ∧ Q) → ∀x (P → ¬Q)
2279                // This converts to implication form for better backward chaining
2280                if let ProofExpr::Not(inner) = body.as_ref() {
2281                    if let ProofExpr::And(left, right) = inner.as_ref() {
2282                        return ProofExpr::ForAll {
2283                            variable: variable.clone(),
2284                            body: Box::new(ProofExpr::Implies(
2285                                Box::new(self.abstract_all_events(left)),
2286                                Box::new(self.abstract_all_events(&ProofExpr::Not(right.clone()))),
2287                            )),
2288                        };
2289                    }
2290                }
2291                ProofExpr::ForAll {
2292                    variable: variable.clone(),
2293                    body: Box::new(self.abstract_all_events(body)),
2294                }
2295            }
2296            ProofExpr::Exists { variable, body } => {
2297                // Check if this is an event quantification that should be abstracted
2298                if self.is_event_variable(variable) {
2299                    if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2300                        return abstracted;
2301                    }
2302                }
2303                // Otherwise keep the existential and recurse
2304                ProofExpr::Exists {
2305                    variable: variable.clone(),
2306                    body: Box::new(self.abstract_all_events(body)),
2307                }
2308            }
2309            // For other expressions, return as-is
2310            other => other.clone(),
2311        }
2312    }
2313
2314    /// Abstract event semantics WITHOUT applying De Morgan transformations.
2315    ///
2316    /// This is used for goals where we want to preserve the ¬∃ pattern
2317    /// for reductio ad absurdum strategies.
2318    fn abstract_events_only(&self, expr: &ProofExpr) -> ProofExpr {
2319        // First try direct abstraction
2320        if let Some(abstracted) = self.abstract_event_to_predicate(expr) {
2321            return abstracted;
2322        }
2323
2324        // Otherwise recurse into the structure
2325        match expr {
2326            ProofExpr::And(left, right) => ProofExpr::And(
2327                Box::new(self.abstract_events_only(left)),
2328                Box::new(self.abstract_events_only(right)),
2329            ),
2330            ProofExpr::Or(left, right) => ProofExpr::Or(
2331                Box::new(self.abstract_events_only(left)),
2332                Box::new(self.abstract_events_only(right)),
2333            ),
2334            ProofExpr::Implies(left, right) => ProofExpr::Implies(
2335                Box::new(self.abstract_events_only(left)),
2336                Box::new(self.abstract_events_only(right)),
2337            ),
2338            ProofExpr::Iff(left, right) => ProofExpr::Iff(
2339                Box::new(self.abstract_events_only(left)),
2340                Box::new(self.abstract_events_only(right)),
2341            ),
2342            ProofExpr::Not(inner) => {
2343                // Just recurse, no De Morgan transformation
2344                ProofExpr::Not(Box::new(self.abstract_events_only(inner)))
2345            }
2346            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
2347                variable: variable.clone(),
2348                body: Box::new(self.abstract_events_only(body)),
2349            },
2350            ProofExpr::Exists { variable, body } => {
2351                // Check if this is an event quantification that should be abstracted
2352                if self.is_event_variable(variable) {
2353                    if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2354                        return abstracted;
2355                    }
2356                }
2357                // Otherwise keep the existential and recurse
2358                ProofExpr::Exists {
2359                    variable: variable.clone(),
2360                    body: Box::new(self.abstract_events_only(body)),
2361                }
2362            }
2363            // For other expressions, return as-is
2364            other => other.clone(),
2365        }
2366    }
2367
2368    /// Look for a contradiction in the knowledge base and context.
2369    ///
2370    /// A contradiction exists when both P and ¬P are derivable.
2371    fn find_contradiction(
2372        &mut self,
2373        context: &[ProofExpr],
2374        depth: usize,
2375    ) -> ProofResult<Option<DerivationTree>> {
2376        // Collect all expressions from KB and context
2377        let all_exprs: Vec<ProofExpr> = self.knowledge_base.iter()
2378            .chain(context.iter())
2379            .cloned()
2380            .collect();
2381
2382        // Strategy 1: Look for direct P and ¬P pairs
2383        for expr in &all_exprs {
2384            if let ProofExpr::Not(inner) = expr {
2385                // We have ¬P, check if P exists directly
2386                for other in &all_exprs {
2387                    if exprs_structurally_equal(other, inner) {
2388                        // Found both P and ¬P directly
2389                        let pos_leaf = DerivationTree::leaf(
2390                            (**inner).clone(),
2391                            InferenceRule::PremiseMatch,
2392                        );
2393                        let neg_leaf = DerivationTree::leaf(
2394                            expr.clone(),
2395                            InferenceRule::PremiseMatch,
2396                        );
2397                        return Ok(Some(DerivationTree::new(
2398                            ProofExpr::Atom("⊥".into()),
2399                            InferenceRule::Contradiction,
2400                            vec![pos_leaf, neg_leaf],
2401                        )));
2402                    }
2403                }
2404            }
2405        }
2406
2407        // Strategy 2: Look for implications that derive contradictory results
2408        // Check if context fact P triggers P → ¬P (immediate contradiction)
2409        // Or if P triggers P → Q where ¬Q is also in context
2410        // Note: Extract implications from both top-level and inside ForAll quantifiers
2411        let mut implications: Vec<(ProofExpr, ProofExpr)> = Vec::new();
2412        for e in &all_exprs {
2413            if let ProofExpr::Implies(ante, cons) = e {
2414                implications.push(((**ante).clone(), (**cons).clone()));
2415            }
2416            // Also extract from inside ForAll (important for barber paradox!)
2417            if let ProofExpr::ForAll { body, .. } = e {
2418                if let ProofExpr::Implies(ante, cons) = body.as_ref() {
2419                    implications.push(((**ante).clone(), (**cons).clone()));
2420                }
2421            }
2422        }
2423
2424        // For each fact in the context, see if it triggers contradictory implications
2425        for fact in context {
2426            // Find all implications where fact matches the antecedent
2427            let mut derivable_consequences: Vec<ProofExpr> = Vec::new();
2428
2429            for (ante, cons) in &implications {
2430                // Try to unify the antecedent with the fact
2431                if let Ok(subst) = unify_exprs(fact, ante) {
2432                    let instantiated_cons = apply_subst_to_expr(cons, &subst);
2433                    derivable_consequences.push(instantiated_cons);
2434                }
2435
2436                // Also try matching conjunctive antecedents with multiple facts
2437                if let ProofExpr::And(left, right) = ante {
2438                    // Try to find facts matching both parts of the conjunction
2439                    if let Some(subst) = self.try_match_conjunction_antecedent(
2440                        left, right, &all_exprs
2441                    ) {
2442                        let instantiated_cons = apply_subst_to_expr(cons, &subst);
2443                        if !derivable_consequences.contains(&instantiated_cons) {
2444                            derivable_consequences.push(instantiated_cons);
2445                        }
2446                    }
2447                }
2448            }
2449
2450            // Check if any derived consequence contradicts the triggering fact
2451            for cons in &derivable_consequences {
2452                // Check if cons = ¬fact (the classic barber structure: P → ¬P)
2453                if let ProofExpr::Not(inner) = cons {
2454                    if exprs_structurally_equal(inner, fact) {
2455                        // fact triggered an implication that derives ¬fact
2456                        // This is a contradiction: fact ∧ ¬fact
2457                        let pos_leaf = DerivationTree::leaf(
2458                            fact.clone(),
2459                            InferenceRule::PremiseMatch,
2460                        );
2461                        let neg_leaf = DerivationTree::leaf(
2462                            cons.clone(),
2463                            InferenceRule::ModusPonens,
2464                        );
2465                        return Ok(Some(DerivationTree::new(
2466                            ProofExpr::Atom("⊥".into()),
2467                            InferenceRule::Contradiction,
2468                            vec![pos_leaf, neg_leaf],
2469                        )));
2470                    }
2471                }
2472
2473                // Check if cons contradicts any other fact in context
2474                for other in context {
2475                    if std::ptr::eq(fact as *const _, other as *const _) {
2476                        continue; // Skip the triggering fact itself
2477                    }
2478                    // Check if cons = ¬other
2479                    if let ProofExpr::Not(inner) = cons {
2480                        if exprs_structurally_equal(inner, other) {
2481                            let pos_leaf = DerivationTree::leaf(
2482                                other.clone(),
2483                                InferenceRule::PremiseMatch,
2484                            );
2485                            let neg_leaf = DerivationTree::leaf(
2486                                cons.clone(),
2487                                InferenceRule::ModusPonens,
2488                            );
2489                            return Ok(Some(DerivationTree::new(
2490                                ProofExpr::Atom("⊥".into()),
2491                                InferenceRule::Contradiction,
2492                                vec![pos_leaf, neg_leaf],
2493                            )));
2494                        }
2495                    }
2496                    // Check if other = ¬cons
2497                    if let ProofExpr::Not(inner_other) = other {
2498                        if exprs_structurally_equal(inner_other, cons) {
2499                            let pos_leaf = DerivationTree::leaf(
2500                                cons.clone(),
2501                                InferenceRule::ModusPonens,
2502                            );
2503                            let neg_leaf = DerivationTree::leaf(
2504                                other.clone(),
2505                                InferenceRule::PremiseMatch,
2506                            );
2507                            return Ok(Some(DerivationTree::new(
2508                                ProofExpr::Atom("⊥".into()),
2509                                InferenceRule::Contradiction,
2510                                vec![pos_leaf, neg_leaf],
2511                            )));
2512                        }
2513                    }
2514                }
2515            }
2516
2517            // Check if any pair of consequences contradicts each other
2518            for i in 0..derivable_consequences.len() {
2519                for j in (i + 1)..derivable_consequences.len() {
2520                    let cons1 = &derivable_consequences[i];
2521                    let cons2 = &derivable_consequences[j];
2522
2523                    // Check if cons1 = ¬cons2 or cons2 = ¬cons1
2524                    if let ProofExpr::Not(inner1) = cons1 {
2525                        if exprs_structurally_equal(inner1, cons2) {
2526                            // cons1 = ¬cons2, contradiction!
2527                            let pos_leaf = DerivationTree::leaf(
2528                                cons2.clone(),
2529                                InferenceRule::ModusPonens,
2530                            );
2531                            let neg_leaf = DerivationTree::leaf(
2532                                cons1.clone(),
2533                                InferenceRule::ModusPonens,
2534                            );
2535                            return Ok(Some(DerivationTree::new(
2536                                ProofExpr::Atom("⊥".into()),
2537                                InferenceRule::Contradiction,
2538                                vec![pos_leaf, neg_leaf],
2539                            )));
2540                        }
2541                    }
2542                    if let ProofExpr::Not(inner2) = cons2 {
2543                        if exprs_structurally_equal(inner2, cons1) {
2544                            // cons2 = ¬cons1, contradiction!
2545                            let pos_leaf = DerivationTree::leaf(
2546                                cons1.clone(),
2547                                InferenceRule::ModusPonens,
2548                            );
2549                            let neg_leaf = DerivationTree::leaf(
2550                                cons2.clone(),
2551                                InferenceRule::ModusPonens,
2552                            );
2553                            return Ok(Some(DerivationTree::new(
2554                                ProofExpr::Atom("⊥".into()),
2555                                InferenceRule::Contradiction,
2556                                vec![pos_leaf, neg_leaf],
2557                            )));
2558                        }
2559                    }
2560                }
2561            }
2562        }
2563
2564        // Strategy 3: Try to find self-referential contradictions (like Barber Paradox)
2565        if let Some(proof) = self.find_self_referential_contradiction(context, depth)? {
2566            return Ok(Some(proof));
2567        }
2568
2569        Ok(None)
2570    }
2571
2572    /// Try to match a conjunctive antecedent with facts in the context.
2573    ///
2574    /// For an antecedent like (man(z) ∧ shave(z,z)), we need to find facts
2575    /// that match both parts with consistent variable bindings.
2576    fn try_match_conjunction_antecedent(
2577        &self,
2578        left: &ProofExpr,
2579        right: &ProofExpr,
2580        facts: &[ProofExpr],
2581    ) -> Option<Substitution> {
2582        // Try to find a fact that matches the left part
2583        for fact1 in facts {
2584            if let Ok(subst1) = unify_exprs(fact1, left) {
2585                // Apply this substitution to the right part
2586                let instantiated_right = apply_subst_to_expr(right, &subst1);
2587                // Now look for a fact that matches the instantiated right part
2588                for fact2 in facts {
2589                    if let Ok(subst2) = unify_exprs(fact2, &instantiated_right) {
2590                        // Combine substitutions
2591                        let mut combined = subst1.clone();
2592                        for (k, v) in subst2.iter() {
2593                            combined.insert(k.clone(), v.clone());
2594                        }
2595                        return Some(combined);
2596                    }
2597                }
2598            }
2599        }
2600        // Also try right then left
2601        for fact1 in facts {
2602            if let Ok(subst1) = unify_exprs(fact1, right) {
2603                let instantiated_left = apply_subst_to_expr(left, &subst1);
2604                for fact2 in facts {
2605                    if let Ok(subst2) = unify_exprs(fact2, &instantiated_left) {
2606                        let mut combined = subst1.clone();
2607                        for (k, v) in subst2.iter() {
2608                            combined.insert(k.clone(), v.clone());
2609                        }
2610                        return Some(combined);
2611                    }
2612                }
2613            }
2614        }
2615        None
2616    }
2617
2618    /// Special case: find self-referential contradictions (like the Barber Paradox).
2619    ///
2620    /// Pattern: If we have ∀x(P(x) → Q(b, x)) and ∀x(P(x) → ¬Q(b, x)),
2621    /// then for x = b with P(b), we get Q(b, b) ∧ ¬Q(b, b).
2622    ///
2623    /// This uses direct pattern matching WITHOUT recursive prove_goal calls
2624    /// to avoid infinite recursion.
2625    fn find_self_referential_contradiction(
2626        &mut self,
2627        context: &[ProofExpr],
2628        _depth: usize,
2629    ) -> ProofResult<Option<DerivationTree>> {
2630        // Collect all expressions from KB and context
2631        let all_exprs: Vec<ProofExpr> = self.knowledge_base.iter()
2632            .chain(context.iter())
2633            .cloned()
2634            .collect();
2635
2636        // Look for pairs of universal implications with contradictory conclusions
2637        // that can be instantiated with the same witness
2638        for expr1 in &all_exprs {
2639            if let ProofExpr::ForAll { variable: var1, body: body1 } = expr1 {
2640                if let ProofExpr::Implies(ante1, cons1) = body1.as_ref() {
2641                    for expr2 in &all_exprs {
2642                        if std::ptr::eq(expr1, expr2) {
2643                            continue; // Skip same expression
2644                        }
2645                        if let ProofExpr::ForAll { variable: var2, body: body2 } = expr2 {
2646                            if let ProofExpr::Implies(ante2, cons2) = body2.as_ref() {
2647                                // Check if cons2 = ¬cons1 (structurally)
2648                                if let ProofExpr::Not(neg_cons2) = cons2.as_ref() {
2649                                    // Check if cons1 and neg_cons2 have matching structure
2650                                    // For barber: cons1 = shaves(barber, x), neg_cons2 = shaves(barber, x)
2651
2652                                    // Try instantiating with x = barber (the self-referential case)
2653                                    // We look for constant terms in cons1 that could be witnesses
2654                                    let witnesses = self.extract_constants_from_expr(cons1);
2655
2656                                    for witness_name in &witnesses {
2657                                        let witness = ProofTerm::Constant(witness_name.clone());
2658
2659                                        // Instantiate both antecedents and consequents with this witness
2660                                        let mut subst1 = Substitution::new();
2661                                        subst1.insert(var1.clone(), witness.clone());
2662                                        let ante1_inst = apply_subst_to_expr(ante1, &subst1);
2663                                        let cons1_inst = apply_subst_to_expr(cons1, &subst1);
2664
2665                                        let mut subst2 = Substitution::new();
2666                                        subst2.insert(var2.clone(), witness.clone());
2667                                        let ante2_inst = apply_subst_to_expr(ante2, &subst2);
2668                                        let cons2_inst = apply_subst_to_expr(cons2, &subst2);
2669
2670                                        // Check if cons1_inst and ¬cons2_inst contradict
2671                                        // cons2_inst should be ¬X where X = cons1_inst
2672                                        if let ProofExpr::Not(inner2) = &cons2_inst {
2673                                            if exprs_structurally_equal(&cons1_inst, inner2) {
2674                                                // Now check if both antecedents could hold
2675                                                // ante1 typically is ¬P(x,x) and ante2 is P(x,x)
2676                                                // These are complementary - one must hold
2677                                                // For the paradox, we consider BOTH cases
2678
2679                                                // If ante1 = ¬P(x,x) and ante2 = P(x,x), and x = witness,
2680                                                // we have a tertium non datur case:
2681                                                // - Either P(w,w) holds → cons2_inst = ¬cons1_inst
2682                                                // - Or ¬P(w,w) holds → cons1_inst
2683
2684                                                // Check if ante1 and ante2 are complements
2685                                                if self.are_complements(&ante1_inst, &ante2_inst) {
2686                                                    // By excluded middle, one antecedent holds
2687                                                    // If cons1_inst and cons2_inst = ¬cons1_inst,
2688                                                    // we have a contradiction
2689                                                    let pos_leaf = DerivationTree::leaf(
2690                                                        cons1_inst.clone(),
2691                                                        InferenceRule::ModusPonens,
2692                                                    );
2693                                                    let neg_leaf = DerivationTree::leaf(
2694                                                        cons2_inst,
2695                                                        InferenceRule::ModusPonens,
2696                                                    );
2697                                                    return Ok(Some(DerivationTree::new(
2698                                                        ProofExpr::Atom("⊥".into()),
2699                                                        InferenceRule::Contradiction,
2700                                                        vec![pos_leaf, neg_leaf],
2701                                                    )));
2702                                                }
2703                                            }
2704                                        }
2705                                    }
2706                                }
2707                            }
2708                        }
2709                    }
2710                }
2711            }
2712        }
2713
2714        Ok(None)
2715    }
2716
2717    /// Check if two expressions are complements (one is the negation of the other).
2718    fn are_complements(&self, expr1: &ProofExpr, expr2: &ProofExpr) -> bool {
2719        // Check if expr1 = ¬expr2
2720        if let ProofExpr::Not(inner1) = expr1 {
2721            if exprs_structurally_equal(inner1, expr2) {
2722                return true;
2723            }
2724        }
2725        // Check if expr2 = ¬expr1
2726        if let ProofExpr::Not(inner2) = expr2 {
2727            if exprs_structurally_equal(inner2, expr1) {
2728                return true;
2729            }
2730        }
2731        false
2732    }
2733
2734    /// Extract constant names from an expression.
2735    fn extract_constants_from_expr(&self, expr: &ProofExpr) -> Vec<String> {
2736        let mut constants = Vec::new();
2737        self.extract_constants_recursive(expr, &mut constants);
2738        constants
2739    }
2740
2741    fn extract_constants_recursive(&self, expr: &ProofExpr, constants: &mut Vec<String>) {
2742        match expr {
2743            ProofExpr::Predicate { args, .. } => {
2744                for arg in args {
2745                    self.extract_constants_from_term_recursive(arg, constants);
2746                }
2747            }
2748            ProofExpr::Identity(l, r) => {
2749                self.extract_constants_from_term_recursive(l, constants);
2750                self.extract_constants_from_term_recursive(r, constants);
2751            }
2752            ProofExpr::And(l, r)
2753            | ProofExpr::Or(l, r)
2754            | ProofExpr::Implies(l, r)
2755            | ProofExpr::Iff(l, r) => {
2756                self.extract_constants_recursive(l, constants);
2757                self.extract_constants_recursive(r, constants);
2758            }
2759            ProofExpr::Not(inner) => {
2760                self.extract_constants_recursive(inner, constants);
2761            }
2762            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
2763                self.extract_constants_recursive(body, constants);
2764            }
2765            _ => {}
2766        }
2767    }
2768
2769    fn extract_constants_from_term_recursive(&self, term: &ProofTerm, constants: &mut Vec<String>) {
2770        match term {
2771            ProofTerm::Constant(name) => {
2772                if !constants.contains(name) {
2773                    constants.push(name.clone());
2774                }
2775            }
2776            ProofTerm::Function(_, args) => {
2777                for arg in args {
2778                    self.extract_constants_from_term_recursive(arg, constants);
2779                }
2780            }
2781            ProofTerm::Group(terms) => {
2782                for t in terms {
2783                    self.extract_constants_from_term_recursive(t, constants);
2784                }
2785            }
2786            _ => {}
2787        }
2788    }
2789
2790    // =========================================================================
2791    // STRATEGY 6: EQUALITY REWRITING (LEIBNIZ'S LAW)
2792    // =========================================================================
2793
2794    /// Try rewriting using equalities in the knowledge base.
2795    ///
2796    /// Leibniz's Law: If a = b and P(a), then P(b).
2797    /// Also handles symmetry (a = b ⊢ b = a) and transitivity (a = b, b = c ⊢ a = c).
2798    fn try_equality_rewrite(
2799        &mut self,
2800        goal: &ProofGoal,
2801        depth: usize,
2802    ) -> ProofResult<Option<DerivationTree>> {
2803        // Collect equalities from KB and context
2804        let equalities: Vec<(ProofTerm, ProofTerm)> = self
2805            .knowledge_base
2806            .iter()
2807            .chain(goal.context.iter())
2808            .filter_map(|expr| {
2809                if let ProofExpr::Identity(l, r) = expr {
2810                    Some((l.clone(), r.clone()))
2811                } else {
2812                    None
2813                }
2814            })
2815            .collect();
2816
2817        if equalities.is_empty() {
2818            return Ok(None);
2819        }
2820
2821        // Handle special case: goal is itself an equality (symmetry/transitivity)
2822        if let ProofExpr::Identity(goal_l, goal_r) = &goal.target {
2823            // Try symmetry: a = b ⊢ b = a
2824            if let Some(tree) = self.try_equality_symmetry(goal_l, goal_r, &equalities, depth)? {
2825                return Ok(Some(tree));
2826            }
2827
2828            // Try transitivity: a = b, b = c ⊢ a = c
2829            if let Some(tree) = self.try_equality_transitivity(goal_l, goal_r, &equalities, depth)? {
2830                return Ok(Some(tree));
2831            }
2832
2833            // Try equational rewriting: use axioms to rewrite LHS step by step
2834            // Only if we have depth budget remaining (prevents infinite recursion)
2835            if depth + 3 < self.max_depth {
2836                if let Some(tree) = self.try_equational_identity_rewrite(goal, goal_l, goal_r, depth)? {
2837                    return Ok(Some(tree));
2838                }
2839            }
2840
2841            return Ok(None);
2842        }
2843
2844        // Try rewriting: substitute one term for another (for non-Identity goals)
2845        for (eq_from, eq_to) in &equalities {
2846            // Try forward: a = b, P(a) ⊢ P(b)
2847            if let Some(tree) = self.try_rewrite_with_equality(
2848                goal, eq_from, eq_to, depth,
2849            )? {
2850                return Ok(Some(tree));
2851            }
2852
2853            // Try backward: a = b, P(b) ⊢ P(a)
2854            if let Some(tree) = self.try_rewrite_with_equality(
2855                goal, eq_to, eq_from, depth,
2856            )? {
2857                return Ok(Some(tree));
2858            }
2859        }
2860
2861        Ok(None)
2862    }
2863
2864    /// Try to prove goal by substituting `from` with `to` in some known fact.
2865    fn try_rewrite_with_equality(
2866        &mut self,
2867        goal: &ProofGoal,
2868        from: &ProofTerm,
2869        to: &ProofTerm,
2870        depth: usize,
2871    ) -> ProofResult<Option<DerivationTree>> {
2872        // Create the "source" expression by substituting `to` with `from` in the goal
2873        // If goal is P(b) and we have a = b, we want to find P(a)
2874        let source_goal = self.substitute_term_in_expr(&goal.target, to, from);
2875
2876        // Check if source_goal differs from the goal (substitution had effect)
2877        if source_goal == goal.target {
2878            return Ok(None);
2879        }
2880
2881        // Try to prove the source goal
2882        let source_proof_goal = ProofGoal::with_context(source_goal.clone(), goal.context.clone());
2883        if let Ok(source_proof) = self.prove_goal(source_proof_goal, depth + 1) {
2884            // Also need a proof of the equality
2885            let equality = ProofExpr::Identity(from.clone(), to.clone());
2886            let eq_proof_goal = ProofGoal::with_context(equality.clone(), goal.context.clone());
2887
2888            if let Ok(eq_proof) = self.prove_goal(eq_proof_goal, depth + 1) {
2889                return Ok(Some(DerivationTree::new(
2890                    goal.target.clone(),
2891                    InferenceRule::Rewrite {
2892                        from: from.clone(),
2893                        to: to.clone(),
2894                    },
2895                    vec![eq_proof, source_proof],
2896                )));
2897            }
2898        }
2899
2900        Ok(None)
2901    }
2902
2903    /// Try equality symmetry: a = b ⊢ b = a
2904    fn try_equality_symmetry(
2905        &mut self,
2906        goal_l: &ProofTerm,
2907        goal_r: &ProofTerm,
2908        equalities: &[(ProofTerm, ProofTerm)],
2909        _depth: usize,
2910    ) -> ProofResult<Option<DerivationTree>> {
2911        // Check if we have r = l in KB (so we can derive l = r)
2912        for (eq_l, eq_r) in equalities {
2913            if eq_l == goal_r && eq_r == goal_l {
2914                // Found r = l, can derive l = r by symmetry
2915                let source = ProofExpr::Identity(goal_r.clone(), goal_l.clone());
2916                return Ok(Some(DerivationTree::new(
2917                    ProofExpr::Identity(goal_l.clone(), goal_r.clone()),
2918                    InferenceRule::EqualitySymmetry,
2919                    vec![DerivationTree::leaf(source, InferenceRule::PremiseMatch)],
2920                )));
2921            }
2922        }
2923        Ok(None)
2924    }
2925
2926    /// Try equality transitivity: a = b, b = c ⊢ a = c
2927    fn try_equality_transitivity(
2928        &mut self,
2929        goal_l: &ProofTerm,
2930        goal_r: &ProofTerm,
2931        equalities: &[(ProofTerm, ProofTerm)],
2932        _depth: usize,
2933    ) -> ProofResult<Option<DerivationTree>> {
2934        // Look for a = b and b = c where we want a = c
2935        for (eq1_l, eq1_r) in equalities {
2936            if eq1_l == goal_l {
2937                // Found a = b, now look for b = c
2938                for (eq2_l, eq2_r) in equalities {
2939                    if eq2_l == eq1_r && eq2_r == goal_r {
2940                        // Found a = b and b = c, derive a = c
2941                        let premise1 = ProofExpr::Identity(eq1_l.clone(), eq1_r.clone());
2942                        let premise2 = ProofExpr::Identity(eq2_l.clone(), eq2_r.clone());
2943                        return Ok(Some(DerivationTree::new(
2944                            ProofExpr::Identity(goal_l.clone(), goal_r.clone()),
2945                            InferenceRule::EqualityTransitivity,
2946                            vec![
2947                                DerivationTree::leaf(premise1, InferenceRule::PremiseMatch),
2948                                DerivationTree::leaf(premise2, InferenceRule::PremiseMatch),
2949                            ],
2950                        )));
2951                    }
2952                }
2953            }
2954        }
2955        Ok(None)
2956    }
2957
2958    /// Try equational rewriting for Identity goals.
2959    ///
2960    /// For a goal `f(a) = b`, find an axiom `f(x) = g(x)` that matches,
2961    /// rewrite to get `g(a) = b`, and recursively prove that.
2962    fn try_equational_identity_rewrite(
2963        &mut self,
2964        goal: &ProofGoal,
2965        goal_l: &ProofTerm,
2966        goal_r: &ProofTerm,
2967        depth: usize,
2968    ) -> ProofResult<Option<DerivationTree>> {
2969        // First, try congruence: if both sides have the same outermost function/ctor,
2970        // recursively prove the arguments are equal.
2971        if let (
2972            ProofTerm::Function(name_l, args_l),
2973            ProofTerm::Function(name_r, args_r),
2974        ) = (goal_l, goal_r)
2975        {
2976            if name_l == name_r && args_l.len() == args_r.len() {
2977                // All arguments must be equal
2978                let mut arg_proofs = Vec::new();
2979                let mut all_ok = true;
2980                for (arg_l, arg_r) in args_l.iter().zip(args_r.iter()) {
2981                    let arg_goal_expr = ProofExpr::Identity(arg_l.clone(), arg_r.clone());
2982                    let arg_goal = ProofGoal::with_context(arg_goal_expr, goal.context.clone());
2983                    match self.prove_goal(arg_goal, depth + 1) {
2984                        Ok(proof) => arg_proofs.push(proof),
2985                        Err(_) => {
2986                            all_ok = false;
2987                            break;
2988                        }
2989                    }
2990                }
2991                if all_ok {
2992                    // All arguments are equal, so the functions are equal by congruence
2993                    return Ok(Some(DerivationTree::new(
2994                        goal.target.clone(),
2995                        InferenceRule::Reflexivity, // Using Reflexivity for congruence
2996                        arg_proofs,
2997                    )));
2998                }
2999            }
3000        }
3001        // Collect Identity axioms from KB
3002        let axioms: Vec<(ProofTerm, ProofTerm)> = self
3003            .knowledge_base
3004            .iter()
3005            .filter_map(|e| {
3006                if let ProofExpr::Identity(l, r) = e {
3007                    Some((l.clone(), r.clone()))
3008                } else {
3009                    None
3010                }
3011            })
3012            .collect();
3013
3014        // Try each axiom to rewrite the goal's LHS
3015        for (axiom_l, axiom_r) in &axioms {
3016            // Rename variables in axiom to avoid capture (use same map for both sides!)
3017            let mut var_map = std::collections::HashMap::new();
3018            let renamed_l = self.rename_term_vars_with_map(axiom_l, &mut var_map);
3019            let renamed_r = self.rename_term_vars_with_map(axiom_r, &mut var_map);
3020
3021            // Try to unify axiom LHS with goal LHS
3022            // e.g., unify(Add(Succ(k), n), Add(Succ(Zero), Succ(Zero)))
3023            //       => {k: Zero, n: Succ(Zero)}
3024            if let Ok(subst) = unify_terms(&renamed_l, goal_l) {
3025                // Apply substitution to axiom RHS to get the rewritten term
3026                let rewritten = self.apply_subst_to_term(&renamed_r, &subst);
3027
3028                // First check: does rewritten equal goal_r directly?
3029                if terms_structurally_equal(&rewritten, goal_r) {
3030                    // Direct match! Build the proof
3031                    let axiom_expr = ProofExpr::Identity(axiom_l.clone(), axiom_r.clone());
3032                    return Ok(Some(DerivationTree::new(
3033                        goal.target.clone(),
3034                        InferenceRule::Rewrite {
3035                            from: goal_l.clone(),
3036                            to: rewritten,
3037                        },
3038                        vec![DerivationTree::leaf(axiom_expr, InferenceRule::PremiseMatch)],
3039                    )));
3040                }
3041
3042                // Otherwise, create a new goal with the rewritten LHS
3043                let new_goal_expr = ProofExpr::Identity(rewritten.clone(), goal_r.clone());
3044                let new_goal = ProofGoal::with_context(new_goal_expr.clone(), goal.context.clone());
3045
3046                // Recursively try to prove the new goal
3047                if let Ok(sub_proof) = self.prove_goal(new_goal, depth + 1) {
3048                    // Success! Build the full proof
3049                    let axiom_expr = ProofExpr::Identity(axiom_l.clone(), axiom_r.clone());
3050                    return Ok(Some(DerivationTree::new(
3051                        goal.target.clone(),
3052                        InferenceRule::Rewrite {
3053                            from: goal_l.clone(),
3054                            to: rewritten,
3055                        },
3056                        vec![
3057                            DerivationTree::leaf(axiom_expr, InferenceRule::PremiseMatch),
3058                            sub_proof,
3059                        ],
3060                    )));
3061                }
3062            }
3063        }
3064
3065        Ok(None)
3066    }
3067
3068    /// Rename variables in a term to fresh names (consistently).
3069    fn rename_term_vars(&mut self, term: &ProofTerm) -> ProofTerm {
3070        let mut var_map = std::collections::HashMap::new();
3071        self.rename_term_vars_with_map(term, &mut var_map)
3072    }
3073
3074    fn rename_term_vars_with_map(
3075        &mut self,
3076        term: &ProofTerm,
3077        var_map: &mut std::collections::HashMap<String, String>,
3078    ) -> ProofTerm {
3079        match term {
3080            ProofTerm::Variable(name) => {
3081                // Check if we've already renamed this variable
3082                if let Some(fresh) = var_map.get(name) {
3083                    ProofTerm::Variable(fresh.clone())
3084                } else {
3085                    // Create fresh name and remember it
3086                    let fresh = format!("_v{}", self.var_counter);
3087                    self.var_counter += 1;
3088                    var_map.insert(name.clone(), fresh.clone());
3089                    ProofTerm::Variable(fresh)
3090                }
3091            }
3092            ProofTerm::Function(name, args) => {
3093                ProofTerm::Function(
3094                    name.clone(),
3095                    args.iter().map(|a| self.rename_term_vars_with_map(a, var_map)).collect(),
3096                )
3097            }
3098            ProofTerm::Group(terms) => {
3099                ProofTerm::Group(
3100                    terms.iter().map(|t| self.rename_term_vars_with_map(t, var_map)).collect(),
3101                )
3102            }
3103            other => other.clone(),
3104        }
3105    }
3106
3107    /// Apply a substitution to a term.
3108    fn apply_subst_to_term(&self, term: &ProofTerm, subst: &Substitution) -> ProofTerm {
3109        match term {
3110            ProofTerm::Variable(name) => {
3111                if let Some(replacement) = subst.get(name) {
3112                    replacement.clone()
3113                } else {
3114                    term.clone()
3115                }
3116            }
3117            ProofTerm::Function(name, args) => {
3118                ProofTerm::Function(
3119                    name.clone(),
3120                    args.iter().map(|a| self.apply_subst_to_term(a, subst)).collect(),
3121                )
3122            }
3123            ProofTerm::Group(terms) => {
3124                ProofTerm::Group(terms.iter().map(|t| self.apply_subst_to_term(t, subst)).collect())
3125            }
3126            other => other.clone(),
3127        }
3128    }
3129
3130    /// Substitute a term for another in an expression.
3131    fn substitute_term_in_expr(
3132        &self,
3133        expr: &ProofExpr,
3134        from: &ProofTerm,
3135        to: &ProofTerm,
3136    ) -> ProofExpr {
3137        match expr {
3138            ProofExpr::Predicate { name, args, world } => {
3139                let new_args: Vec<_> = args
3140                    .iter()
3141                    .map(|arg| self.substitute_in_term(arg, from, to))
3142                    .collect();
3143                ProofExpr::Predicate {
3144                    name: name.clone(),
3145                    args: new_args,
3146                    world: world.clone(),
3147                }
3148            }
3149            ProofExpr::Identity(l, r) => ProofExpr::Identity(
3150                self.substitute_in_term(l, from, to),
3151                self.substitute_in_term(r, from, to),
3152            ),
3153            ProofExpr::And(l, r) => ProofExpr::And(
3154                Box::new(self.substitute_term_in_expr(l, from, to)),
3155                Box::new(self.substitute_term_in_expr(r, from, to)),
3156            ),
3157            ProofExpr::Or(l, r) => ProofExpr::Or(
3158                Box::new(self.substitute_term_in_expr(l, from, to)),
3159                Box::new(self.substitute_term_in_expr(r, from, to)),
3160            ),
3161            ProofExpr::Implies(l, r) => ProofExpr::Implies(
3162                Box::new(self.substitute_term_in_expr(l, from, to)),
3163                Box::new(self.substitute_term_in_expr(r, from, to)),
3164            ),
3165            ProofExpr::Not(inner) => {
3166                ProofExpr::Not(Box::new(self.substitute_term_in_expr(inner, from, to)))
3167            }
3168            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
3169                variable: variable.clone(),
3170                body: Box::new(self.substitute_term_in_expr(body, from, to)),
3171            },
3172            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
3173                variable: variable.clone(),
3174                body: Box::new(self.substitute_term_in_expr(body, from, to)),
3175            },
3176            // For other expressions, return as-is
3177            other => other.clone(),
3178        }
3179    }
3180
3181    /// Substitute a term for another in a ProofTerm.
3182    fn substitute_in_term(
3183        &self,
3184        term: &ProofTerm,
3185        from: &ProofTerm,
3186        to: &ProofTerm,
3187    ) -> ProofTerm {
3188        if term == from {
3189            return to.clone();
3190        }
3191        match term {
3192            ProofTerm::Function(name, args) => {
3193                let new_args: Vec<_> = args
3194                    .iter()
3195                    .map(|arg| self.substitute_in_term(arg, from, to))
3196                    .collect();
3197                ProofTerm::Function(name.clone(), new_args)
3198            }
3199            ProofTerm::Group(terms) => {
3200                let new_terms: Vec<_> = terms
3201                    .iter()
3202                    .map(|t| self.substitute_in_term(t, from, to))
3203                    .collect();
3204                ProofTerm::Group(new_terms)
3205            }
3206            other => other.clone(),
3207        }
3208    }
3209
3210    // =========================================================================
3211    // STRATEGY 7: STRUCTURAL INDUCTION
3212    // =========================================================================
3213
3214    /// Try structural induction on inductive types (Nat, List, etc.).
3215    ///
3216    /// First attempts to infer the motive using Miller pattern unification
3217    /// (`?Motive(#n) = Goal` → `?Motive = λn.Goal`). Falls back to crude
3218    /// substitution if pattern unification fails.
3219    ///
3220    /// When the goal contains a TypedVar like `n:Nat`, we split into:
3221    /// - Base case: P(Zero)
3222    /// - Step case: ∀k. P(k) → P(Succ(k))
3223    fn try_structural_induction(
3224        &mut self,
3225        goal: &ProofGoal,
3226        depth: usize,
3227    ) -> ProofResult<Option<DerivationTree>> {
3228        // Look for TypedVar in the goal
3229        if let Some((var_name, typename)) = self.find_typed_var(&goal.target) {
3230            // Try motive inference via pattern unification first
3231            if let Some(motive) = self.try_infer_motive(&goal.target, &var_name) {
3232                match typename.as_str() {
3233                    "Nat" => {
3234                        if let Ok(Some(proof)) =
3235                            self.try_nat_induction_with_motive(goal, &var_name, &motive, depth)
3236                        {
3237                            return Ok(Some(proof));
3238                        }
3239                    }
3240                    "List" => {
3241                        // TODO: Add try_list_induction_with_motive
3242                    }
3243                    _ => {}
3244                }
3245            }
3246
3247            // Fallback: crude substitution approach
3248            match typename.as_str() {
3249                "Nat" => self.try_nat_induction(goal, &var_name, depth),
3250                "List" => self.try_list_induction(goal, &var_name, depth),
3251                _ => Ok(None), // Unknown inductive type
3252            }
3253        } else {
3254            Ok(None)
3255        }
3256    }
3257
3258    /// Try to infer the induction motive using Miller pattern unification.
3259    ///
3260    /// Given a goal like `Add(n:Nat, Zero) = n:Nat`, creates the pattern
3261    /// `?Motive(#n) = Goal` and solves for `?Motive = λn. Goal`.
3262    fn try_infer_motive(&self, goal: &ProofExpr, var_name: &str) -> Option<ProofExpr> {
3263        // Create the pattern: ?Motive(#var_name)
3264        let motive_hole = ProofExpr::Hole("Motive".to_string());
3265        let pattern = ProofExpr::App(
3266            Box::new(motive_hole),
3267            Box::new(ProofExpr::Term(ProofTerm::BoundVarRef(var_name.to_string()))),
3268        );
3269
3270        // The body is the goal itself (with TypedVar replaced by Variable for unification)
3271        let body = self.convert_typed_var_to_variable(goal, var_name);
3272
3273        // Unify: ?Motive(#n) = body
3274        match unify_pattern(&pattern, &body) {
3275            Ok(solution) => solution.get("Motive").cloned(),
3276            Err(_) => None,
3277        }
3278    }
3279
3280    /// Convert TypedVar to regular Variable for pattern unification.
3281    ///
3282    /// Pattern unification expects Variable("n") in the body to match BoundVarRef("n")
3283    /// in the pattern, but our goals have TypedVar { name: "n", typename: "Nat" }.
3284    fn convert_typed_var_to_variable(&self, expr: &ProofExpr, var_name: &str) -> ProofExpr {
3285        match expr {
3286            ProofExpr::TypedVar { name, .. } if name == var_name => {
3287                // Convert to Atom so it becomes a Variable in terms
3288                ProofExpr::Atom(name.clone())
3289            }
3290            ProofExpr::Identity(l, r) => ProofExpr::Identity(
3291                self.convert_typed_var_in_term(l, var_name),
3292                self.convert_typed_var_in_term(r, var_name),
3293            ),
3294            ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
3295                name: name.clone(),
3296                args: args
3297                    .iter()
3298                    .map(|a| self.convert_typed_var_in_term(a, var_name))
3299                    .collect(),
3300                world: world.clone(),
3301            },
3302            ProofExpr::And(l, r) => ProofExpr::And(
3303                Box::new(self.convert_typed_var_to_variable(l, var_name)),
3304                Box::new(self.convert_typed_var_to_variable(r, var_name)),
3305            ),
3306            ProofExpr::Or(l, r) => ProofExpr::Or(
3307                Box::new(self.convert_typed_var_to_variable(l, var_name)),
3308                Box::new(self.convert_typed_var_to_variable(r, var_name)),
3309            ),
3310            ProofExpr::Not(inner) => {
3311                ProofExpr::Not(Box::new(self.convert_typed_var_to_variable(inner, var_name)))
3312            }
3313            _ => expr.clone(),
3314        }
3315    }
3316
3317    /// Convert TypedVar to Variable in a ProofTerm.
3318    fn convert_typed_var_in_term(&self, term: &ProofTerm, var_name: &str) -> ProofTerm {
3319        match term {
3320            ProofTerm::Variable(v) => {
3321                // Check for "name:Type" pattern
3322                if v == var_name || v.starts_with(&format!("{}:", var_name)) {
3323                    ProofTerm::Variable(var_name.to_string())
3324                } else {
3325                    term.clone()
3326                }
3327            }
3328            ProofTerm::Function(name, args) => ProofTerm::Function(
3329                name.clone(),
3330                args.iter()
3331                    .map(|a| self.convert_typed_var_in_term(a, var_name))
3332                    .collect(),
3333            ),
3334            ProofTerm::Group(terms) => ProofTerm::Group(
3335                terms
3336                    .iter()
3337                    .map(|t| self.convert_typed_var_in_term(t, var_name))
3338                    .collect(),
3339            ),
3340            _ => term.clone(),
3341        }
3342    }
3343
3344    /// Perform structural induction on Nat using pattern unification.
3345    ///
3346    /// Uses Miller pattern unification to infer the motive, then applies
3347    /// it to constructors via beta reduction.
3348    ///
3349    /// Base case: P(Zero)
3350    /// Step case: ∀k. P(k) → P(Succ(k))
3351    fn try_nat_induction_with_motive(
3352        &mut self,
3353        goal: &ProofGoal,
3354        var_name: &str,
3355        motive: &ProofExpr,
3356        depth: usize,
3357    ) -> ProofResult<Option<DerivationTree>> {
3358        // Base case: P(Zero)
3359        // Apply the motive lambda to Zero constructor
3360        let zero_ctor = ProofExpr::Ctor {
3361            name: "Zero".into(),
3362            args: vec![],
3363        };
3364        let base_goal_expr = beta_reduce(&ProofExpr::App(
3365            Box::new(motive.clone()),
3366            Box::new(zero_ctor),
3367        ));
3368
3369        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3370        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3371            Ok(proof) => proof,
3372            Err(_) => return Ok(None),
3373        };
3374
3375        // Step case: ∀k. P(k) → P(Succ(k))
3376        let fresh_k = self.fresh_var();
3377        let k_var = ProofExpr::Atom(fresh_k.clone());
3378
3379        // Induction hypothesis: P(k)
3380        let ih = beta_reduce(&ProofExpr::App(
3381            Box::new(motive.clone()),
3382            Box::new(k_var.clone()),
3383        ));
3384
3385        // Step conclusion: P(Succ(k))
3386        let succ_k = ProofExpr::Ctor {
3387            name: "Succ".into(),
3388            args: vec![k_var],
3389        };
3390        let step_goal_expr = beta_reduce(&ProofExpr::App(
3391            Box::new(motive.clone()),
3392            Box::new(succ_k),
3393        ));
3394
3395        // Add IH to context for step case
3396        let mut step_context = goal.context.clone();
3397        step_context.push(ih.clone());
3398
3399        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3400        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3401        {
3402            Ok(proof) => proof,
3403            Err(_) => return Ok(None),
3404        };
3405
3406        Ok(Some(DerivationTree::new(
3407            goal.target.clone(),
3408            InferenceRule::StructuralInduction {
3409                variable: var_name.to_string(),
3410                ind_type: "Nat".to_string(),
3411                step_var: fresh_k,
3412            },
3413            vec![base_proof, step_proof],
3414        )))
3415    }
3416
3417    /// Perform structural induction on Nat (legacy crude substitution).
3418    ///
3419    /// Base case: P(Zero)
3420    /// Step case: ∀k. P(k) → P(Succ(k))
3421    fn try_nat_induction(
3422        &mut self,
3423        goal: &ProofGoal,
3424        var_name: &str,
3425        depth: usize,
3426    ) -> ProofResult<Option<DerivationTree>> {
3427        // Create Zero constructor
3428        let zero = ProofExpr::Ctor {
3429            name: "Zero".into(),
3430            args: vec![],
3431        };
3432
3433        // Base case: substitute Zero for the induction variable
3434        let base_goal_expr = self.substitute_typed_var(&goal.target, var_name, &zero);
3435        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3436
3437        // Try to prove base case
3438        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3439            Ok(proof) => proof,
3440            Err(_) => return Ok(None), // Can't prove base case
3441        };
3442
3443        // Step case: assume P(k), prove P(Succ(k))
3444        let fresh_k = self.fresh_var();
3445
3446        // Create k as a variable
3447        let k_var = ProofExpr::Atom(fresh_k.clone());
3448
3449        // Create Succ(k)
3450        let succ_k = ProofExpr::Ctor {
3451            name: "Succ".into(),
3452            args: vec![k_var.clone()],
3453        };
3454
3455        // Induction hypothesis: P(k)
3456        let ih = self.substitute_typed_var(&goal.target, var_name, &k_var);
3457
3458        // Step goal: P(Succ(k))
3459        let step_goal_expr = self.substitute_typed_var(&goal.target, var_name, &succ_k);
3460
3461        // Add IH to context for step case
3462        let mut step_context = goal.context.clone();
3463        step_context.push(ih.clone());
3464
3465        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3466
3467        // Try to prove step case with IH in context
3468        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3469        {
3470            Ok(proof) => proof,
3471            Err(_) => return Ok(None), // Can't prove step case
3472        };
3473
3474        // Build the induction proof tree
3475        Ok(Some(DerivationTree::new(
3476            goal.target.clone(),
3477            InferenceRule::StructuralInduction {
3478                variable: var_name.to_string(),
3479                ind_type: "Nat".to_string(),
3480                step_var: fresh_k,
3481            },
3482            vec![base_proof, step_proof],
3483        )))
3484    }
3485
3486    /// Perform structural induction on List.
3487    ///
3488    /// Base case: P(Nil)
3489    /// Step case: ∀h,t. P(t) → P(Cons(h,t))
3490    fn try_list_induction(
3491        &mut self,
3492        goal: &ProofGoal,
3493        var_name: &str,
3494        depth: usize,
3495    ) -> ProofResult<Option<DerivationTree>> {
3496        // Create Nil constructor
3497        let nil = ProofExpr::Ctor {
3498            name: "Nil".into(),
3499            args: vec![],
3500        };
3501
3502        // Base case: substitute Nil for the induction variable
3503        let base_goal_expr = self.substitute_typed_var(&goal.target, var_name, &nil);
3504        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3505
3506        // Try to prove base case
3507        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3508            Ok(proof) => proof,
3509            Err(_) => return Ok(None),
3510        };
3511
3512        // Step case: assume P(t), prove P(Cons(h, t))
3513        let fresh_h = self.fresh_var();
3514        let fresh_t = self.fresh_var();
3515
3516        let h_var = ProofExpr::Atom(fresh_h);
3517        let t_var = ProofExpr::Atom(fresh_t.clone());
3518
3519        let cons_ht = ProofExpr::Ctor {
3520            name: "Cons".into(),
3521            args: vec![h_var, t_var.clone()],
3522        };
3523
3524        // Induction hypothesis: P(t)
3525        let ih = self.substitute_typed_var(&goal.target, var_name, &t_var);
3526
3527        // Step goal: P(Cons(h, t))
3528        let step_goal_expr = self.substitute_typed_var(&goal.target, var_name, &cons_ht);
3529
3530        let mut step_context = goal.context.clone();
3531        step_context.push(ih.clone());
3532
3533        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3534
3535        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3536        {
3537            Ok(proof) => proof,
3538            Err(_) => return Ok(None),
3539        };
3540
3541        Ok(Some(DerivationTree::new(
3542            goal.target.clone(),
3543            InferenceRule::StructuralInduction {
3544                variable: var_name.to_string(),
3545                ind_type: "List".to_string(),
3546                step_var: fresh_t,
3547            },
3548            vec![base_proof, step_proof],
3549        )))
3550    }
3551
3552    /// Try to prove the step case, potentially using equational reasoning.
3553    ///
3554    /// The step case often requires:
3555    /// 1. Applying a recursive axiom to simplify the goal
3556    /// 2. Using the induction hypothesis
3557    /// 3. Congruence reasoning (e.g., Succ(x) = Succ(y) if x = y)
3558    fn try_step_case_with_equational_reasoning(
3559        &mut self,
3560        goal: &ProofGoal,
3561        ih: &ProofExpr,
3562        depth: usize,
3563    ) -> ProofResult<DerivationTree> {
3564        // First, try direct proof (might work for simple cases)
3565        if let Ok(proof) = self.prove_goal(goal.clone(), depth + 1) {
3566            return Ok(proof);
3567        }
3568
3569        // For Identity goals, try equational reasoning
3570        if let ProofExpr::Identity(lhs, rhs) = &goal.target {
3571            // Try to rewrite LHS using axioms and see if we can reach RHS
3572            if let Some(proof) = self.try_equational_proof(goal, lhs, rhs, ih, depth)? {
3573                return Ok(proof);
3574            }
3575        }
3576
3577        Err(ProofError::NoProofFound)
3578    }
3579
3580    /// Try equational reasoning: rewrite LHS to match RHS using axioms and IH.
3581    ///
3582    /// For the step case of induction, we need to:
3583    /// 1. Find an axiom that matches the goal's LHS pattern
3584    /// 2. Use the axiom to rewrite LHS
3585    /// 3. Apply the induction hypothesis to simplify
3586    /// 4. Check if the result equals RHS
3587    fn try_equational_proof(
3588        &mut self,
3589        goal: &ProofGoal,
3590        lhs: &ProofTerm,
3591        rhs: &ProofTerm,
3592        ih: &ProofExpr,
3593        _depth: usize,
3594    ) -> ProofResult<Option<DerivationTree>> {
3595        // Find applicable equations from KB (Identity axioms)
3596        let equations: Vec<ProofExpr> = self
3597            .knowledge_base
3598            .iter()
3599            .filter(|e| matches!(e, ProofExpr::Identity(_, _)))
3600            .cloned()
3601            .collect();
3602
3603        // Try each equation to rewrite LHS
3604        for eq_axiom in &equations {
3605            if let ProofExpr::Identity(_, _) = &eq_axiom {
3606                // Rename variables in the axiom to avoid capture
3607                let renamed_axiom = self.rename_variables(&eq_axiom);
3608                if let ProofExpr::Identity(renamed_lhs, renamed_rhs) = renamed_axiom {
3609                    // Unify axiom LHS with goal LHS
3610                    // This binds axiom variables to goal terms
3611                    // e.g., unify(Add(Succ(x), m), Add(Succ(k), Zero)) gives {x->k, m->Zero}
3612                    if let Ok(subst) = unify_terms(&renamed_lhs, lhs) {
3613                        // Apply the substitution to the axiom's RHS
3614                        // This gives us what LHS rewrites to
3615                        let rewritten = self.apply_subst_to_term_with(&renamed_rhs, &subst);
3616
3617                        // Now check if rewritten equals RHS (possibly using IH)
3618                        if self.terms_equal_with_ih(&rewritten, rhs, ih) {
3619                            // Success! Build proof using the axiom and IH
3620                            let axiom_leaf =
3621                                DerivationTree::leaf(eq_axiom.clone(), InferenceRule::PremiseMatch);
3622
3623                            let ih_leaf =
3624                                DerivationTree::leaf(ih.clone(), InferenceRule::PremiseMatch);
3625
3626                            return Ok(Some(DerivationTree::new(
3627                                goal.target.clone(),
3628                                InferenceRule::PremiseMatch, // Equational step
3629                                vec![axiom_leaf, ih_leaf],
3630                            )));
3631                        }
3632                    }
3633                }
3634            }
3635        }
3636
3637        Ok(None)
3638    }
3639
3640    /// Check if two terms are equal, potentially using the induction hypothesis.
3641    fn terms_equal_with_ih(&self, t1: &ProofTerm, t2: &ProofTerm, ih: &ProofExpr) -> bool {
3642        // Direct equality
3643        if t1 == t2 {
3644            return true;
3645        }
3646
3647        // Try using IH: if IH is `x = y`, and t1 contains x, replace with y
3648        if let ProofExpr::Identity(ih_lhs, ih_rhs) = ih {
3649            // Check if t1 can be transformed to t2 using IH
3650            let t1_with_ih = self.rewrite_term_with_equation(t1, ih_lhs, ih_rhs);
3651            if &t1_with_ih == t2 {
3652                return true;
3653            }
3654
3655            // Also try the other direction
3656            let t2_with_ih = self.rewrite_term_with_equation(t2, ih_rhs, ih_lhs);
3657            if t1 == &t2_with_ih {
3658                return true;
3659            }
3660        }
3661
3662        false
3663    }
3664
3665    /// Rewrite occurrences of `from` to `to` in the term.
3666    fn rewrite_term_with_equation(
3667        &self,
3668        term: &ProofTerm,
3669        from: &ProofTerm,
3670        to: &ProofTerm,
3671    ) -> ProofTerm {
3672        // If term matches `from`, return `to`
3673        if term == from {
3674            return to.clone();
3675        }
3676
3677        // Recursively rewrite in subterms
3678        match term {
3679            ProofTerm::Function(name, args) => {
3680                let new_args: Vec<ProofTerm> = args
3681                    .iter()
3682                    .map(|a| self.rewrite_term_with_equation(a, from, to))
3683                    .collect();
3684                ProofTerm::Function(name.clone(), new_args)
3685            }
3686            ProofTerm::Group(terms) => {
3687                let new_terms: Vec<ProofTerm> = terms
3688                    .iter()
3689                    .map(|t| self.rewrite_term_with_equation(t, from, to))
3690                    .collect();
3691                ProofTerm::Group(new_terms)
3692            }
3693            _ => term.clone(),
3694        }
3695    }
3696
3697    /// Apply substitution to a ProofTerm with given substitution.
3698    fn apply_subst_to_term_with(&self, term: &ProofTerm, subst: &Substitution) -> ProofTerm {
3699        match term {
3700            ProofTerm::Variable(v) => subst.get(v).cloned().unwrap_or_else(|| term.clone()),
3701            ProofTerm::Function(name, args) => ProofTerm::Function(
3702                name.clone(),
3703                args.iter()
3704                    .map(|a| self.apply_subst_to_term_with(a, subst))
3705                    .collect(),
3706            ),
3707            ProofTerm::Group(terms) => ProofTerm::Group(
3708                terms
3709                    .iter()
3710                    .map(|t| self.apply_subst_to_term_with(t, subst))
3711                    .collect(),
3712            ),
3713            ProofTerm::Constant(_) => term.clone(),
3714            ProofTerm::BoundVarRef(_) => term.clone(),
3715        }
3716    }
3717
3718    /// Find a TypedVar in the expression.
3719    fn find_typed_var(&self, expr: &ProofExpr) -> Option<(String, String)> {
3720        match expr {
3721            ProofExpr::TypedVar { name, typename } => Some((name.clone(), typename.clone())),
3722            ProofExpr::Identity(l, r) => {
3723                self.find_typed_var_in_term(l).or_else(|| self.find_typed_var_in_term(r))
3724            }
3725            ProofExpr::Predicate { args, .. } => {
3726                for arg in args {
3727                    if let Some(tv) = self.find_typed_var_in_term(arg) {
3728                        return Some(tv);
3729                    }
3730                }
3731                None
3732            }
3733            ProofExpr::And(l, r)
3734            | ProofExpr::Or(l, r)
3735            | ProofExpr::Implies(l, r)
3736            | ProofExpr::Iff(l, r) => self.find_typed_var(l).or_else(|| self.find_typed_var(r)),
3737            ProofExpr::Not(inner) => self.find_typed_var(inner),
3738            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
3739                self.find_typed_var(body)
3740            }
3741            _ => None,
3742        }
3743    }
3744
3745    /// Find a TypedVar embedded in a ProofTerm.
3746    fn find_typed_var_in_term(&self, term: &ProofTerm) -> Option<(String, String)> {
3747        match term {
3748            ProofTerm::Variable(v) => {
3749                // Check if this variable name is in our KB as a TypedVar
3750                // Actually, TypedVar should be in the expression, not the term
3751                // Let's check if the variable name contains type annotation
3752                if v.contains(':') {
3753                    let parts: Vec<&str> = v.splitn(2, ':').collect();
3754                    if parts.len() == 2 {
3755                        return Some((parts[0].to_string(), parts[1].to_string()));
3756                    }
3757                }
3758                None
3759            }
3760            ProofTerm::Function(_, args) => {
3761                for arg in args {
3762                    if let Some(tv) = self.find_typed_var_in_term(arg) {
3763                        return Some(tv);
3764                    }
3765                }
3766                None
3767            }
3768            ProofTerm::Group(terms) => {
3769                for t in terms {
3770                    if let Some(tv) = self.find_typed_var_in_term(t) {
3771                        return Some(tv);
3772                    }
3773                }
3774                None
3775            }
3776            ProofTerm::Constant(_) => None,
3777            ProofTerm::BoundVarRef(_) => None, // Pattern-level, no TypedVar
3778        }
3779    }
3780
3781    /// Substitute a TypedVar with a given expression throughout the goal.
3782    fn substitute_typed_var(
3783        &self,
3784        expr: &ProofExpr,
3785        var_name: &str,
3786        replacement: &ProofExpr,
3787    ) -> ProofExpr {
3788        match expr {
3789            ProofExpr::TypedVar { name, .. } if name == var_name => replacement.clone(),
3790            ProofExpr::Identity(l, r) => {
3791                let new_l = self.substitute_typed_var_in_term(l, var_name, replacement);
3792                let new_r = self.substitute_typed_var_in_term(r, var_name, replacement);
3793                ProofExpr::Identity(new_l, new_r)
3794            }
3795            ProofExpr::Predicate { name, args, world } => {
3796                let new_args: Vec<ProofTerm> = args
3797                    .iter()
3798                    .map(|a| self.substitute_typed_var_in_term(a, var_name, replacement))
3799                    .collect();
3800                ProofExpr::Predicate {
3801                    name: name.clone(),
3802                    args: new_args,
3803                    world: world.clone(),
3804                }
3805            }
3806            ProofExpr::And(l, r) => ProofExpr::And(
3807                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3808                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3809            ),
3810            ProofExpr::Or(l, r) => ProofExpr::Or(
3811                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3812                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3813            ),
3814            ProofExpr::Implies(l, r) => ProofExpr::Implies(
3815                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3816                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3817            ),
3818            ProofExpr::Iff(l, r) => ProofExpr::Iff(
3819                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3820                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3821            ),
3822            ProofExpr::Not(inner) => {
3823                ProofExpr::Not(Box::new(self.substitute_typed_var(inner, var_name, replacement)))
3824            }
3825            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
3826                variable: variable.clone(),
3827                body: Box::new(self.substitute_typed_var(body, var_name, replacement)),
3828            },
3829            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
3830                variable: variable.clone(),
3831                body: Box::new(self.substitute_typed_var(body, var_name, replacement)),
3832            },
3833            _ => expr.clone(),
3834        }
3835    }
3836
3837    /// Substitute a TypedVar in a ProofTerm.
3838    fn substitute_typed_var_in_term(
3839        &self,
3840        term: &ProofTerm,
3841        var_name: &str,
3842        replacement: &ProofExpr,
3843    ) -> ProofTerm {
3844        match term {
3845            ProofTerm::Variable(v) => {
3846                // Check for TypedVar pattern "name:Type"
3847                if v == var_name || v.starts_with(&format!("{}:", var_name)) {
3848                    self.expr_to_term(replacement)
3849                } else {
3850                    term.clone()
3851                }
3852            }
3853            ProofTerm::Function(name, args) => ProofTerm::Function(
3854                name.clone(),
3855                args.iter()
3856                    .map(|a| self.substitute_typed_var_in_term(a, var_name, replacement))
3857                    .collect(),
3858            ),
3859            ProofTerm::Group(terms) => ProofTerm::Group(
3860                terms
3861                    .iter()
3862                    .map(|t| self.substitute_typed_var_in_term(t, var_name, replacement))
3863                    .collect(),
3864            ),
3865            ProofTerm::Constant(_) => term.clone(),
3866            ProofTerm::BoundVarRef(_) => term.clone(),
3867        }
3868    }
3869
3870    /// Convert a ProofExpr to a ProofTerm (for use in substitution).
3871    fn expr_to_term(&self, expr: &ProofExpr) -> ProofTerm {
3872        match expr {
3873            ProofExpr::Atom(s) => ProofTerm::Variable(s.clone()),
3874            ProofExpr::Ctor { name, args } => {
3875                ProofTerm::Function(name.clone(), args.iter().map(|a| self.expr_to_term(a)).collect())
3876            }
3877            ProofExpr::TypedVar { name, .. } => ProofTerm::Variable(name.clone()),
3878            _ => ProofTerm::Constant(format!("{}", expr)),
3879        }
3880    }
3881
3882    // =========================================================================
3883    // HELPER METHODS
3884    // =========================================================================
3885
3886    /// Generate a fresh variable name.
3887    fn fresh_var(&mut self) -> String {
3888        self.var_counter += 1;
3889        format!("_G{}", self.var_counter)
3890    }
3891
3892    /// Rename all variables in an expression to fresh names.
3893    fn rename_variables(&mut self, expr: &ProofExpr) -> ProofExpr {
3894        let vars = self.collect_variables(expr);
3895        let mut subst = Substitution::new();
3896
3897        for var in vars {
3898            let fresh = self.fresh_var();
3899            subst.insert(var, ProofTerm::Variable(fresh));
3900        }
3901
3902        apply_subst_to_expr(expr, &subst)
3903    }
3904
3905    /// Collect all variable names in an expression.
3906    fn collect_variables(&self, expr: &ProofExpr) -> Vec<String> {
3907        let mut vars = Vec::new();
3908        self.collect_variables_recursive(expr, &mut vars);
3909        vars
3910    }
3911
3912    fn collect_variables_recursive(&self, expr: &ProofExpr, vars: &mut Vec<String>) {
3913        match expr {
3914            ProofExpr::Predicate { args, .. } => {
3915                for arg in args {
3916                    self.collect_term_variables(arg, vars);
3917                }
3918            }
3919            ProofExpr::Identity(l, r) => {
3920                self.collect_term_variables(l, vars);
3921                self.collect_term_variables(r, vars);
3922            }
3923            ProofExpr::And(l, r)
3924            | ProofExpr::Or(l, r)
3925            | ProofExpr::Implies(l, r)
3926            | ProofExpr::Iff(l, r) => {
3927                self.collect_variables_recursive(l, vars);
3928                self.collect_variables_recursive(r, vars);
3929            }
3930            ProofExpr::Not(inner) => self.collect_variables_recursive(inner, vars),
3931            ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body } => {
3932                if !vars.contains(variable) {
3933                    vars.push(variable.clone());
3934                }
3935                self.collect_variables_recursive(body, vars);
3936            }
3937            ProofExpr::Lambda { variable, body } => {
3938                if !vars.contains(variable) {
3939                    vars.push(variable.clone());
3940                }
3941                self.collect_variables_recursive(body, vars);
3942            }
3943            ProofExpr::App(f, a) => {
3944                self.collect_variables_recursive(f, vars);
3945                self.collect_variables_recursive(a, vars);
3946            }
3947            ProofExpr::NeoEvent { roles, .. } => {
3948                for (_, term) in roles {
3949                    self.collect_term_variables(term, vars);
3950                }
3951            }
3952            _ => {}
3953        }
3954    }
3955
3956    fn collect_term_variables(&self, term: &ProofTerm, vars: &mut Vec<String>) {
3957        match term {
3958            ProofTerm::Variable(v) => {
3959                if !vars.contains(v) {
3960                    vars.push(v.clone());
3961                }
3962            }
3963            ProofTerm::Function(_, args) => {
3964                for arg in args {
3965                    self.collect_term_variables(arg, vars);
3966                }
3967            }
3968            ProofTerm::Group(terms) => {
3969                for t in terms {
3970                    self.collect_term_variables(t, vars);
3971                }
3972            }
3973            ProofTerm::Constant(_) => {}
3974            ProofTerm::BoundVarRef(_) => {} // Pattern-level, no variables
3975        }
3976    }
3977
3978    /// Collect potential witnesses (constants) from the knowledge base.
3979    fn collect_witnesses(&self) -> Vec<ProofTerm> {
3980        let mut witnesses = Vec::new();
3981
3982        for expr in &self.knowledge_base {
3983            self.collect_constants_from_expr(expr, &mut witnesses);
3984        }
3985
3986        witnesses
3987    }
3988
3989    fn collect_constants_from_expr(&self, expr: &ProofExpr, constants: &mut Vec<ProofTerm>) {
3990        match expr {
3991            ProofExpr::Predicate { args, .. } => {
3992                for arg in args {
3993                    self.collect_constants_from_term(arg, constants);
3994                }
3995            }
3996            ProofExpr::Identity(l, r) => {
3997                self.collect_constants_from_term(l, constants);
3998                self.collect_constants_from_term(r, constants);
3999            }
4000            ProofExpr::And(l, r)
4001            | ProofExpr::Or(l, r)
4002            | ProofExpr::Implies(l, r)
4003            | ProofExpr::Iff(l, r) => {
4004                self.collect_constants_from_expr(l, constants);
4005                self.collect_constants_from_expr(r, constants);
4006            }
4007            ProofExpr::Not(inner) => self.collect_constants_from_expr(inner, constants),
4008            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
4009                self.collect_constants_from_expr(body, constants);
4010            }
4011            ProofExpr::NeoEvent { roles, .. } => {
4012                for (_, term) in roles {
4013                    self.collect_constants_from_term(term, constants);
4014                }
4015            }
4016            _ => {}
4017        }
4018    }
4019
4020    fn collect_constants_from_term(&self, term: &ProofTerm, constants: &mut Vec<ProofTerm>) {
4021        match term {
4022            ProofTerm::Constant(_) => {
4023                if !constants.contains(term) {
4024                    constants.push(term.clone());
4025                }
4026            }
4027            ProofTerm::Function(_, args) => {
4028                // The function application itself could be a witness
4029                if !constants.contains(term) {
4030                    constants.push(term.clone());
4031                }
4032                for arg in args {
4033                    self.collect_constants_from_term(arg, constants);
4034                }
4035            }
4036            ProofTerm::Group(terms) => {
4037                for t in terms {
4038                    self.collect_constants_from_term(t, constants);
4039                }
4040            }
4041            ProofTerm::Variable(_) => {}
4042            ProofTerm::BoundVarRef(_) => {} // Pattern-level, not a constant
4043        }
4044    }
4045
4046    // =========================================================================
4047    // STRATEGY 7: ORACLE FALLBACK (Z3)
4048    // =========================================================================
4049
4050    /// Attempt to prove using Z3 as an oracle.
4051    ///
4052    /// This is the fallback when all structural proof strategies fail.
4053    /// Z3 will verify arithmetic, comparisons, and uninterpreted function reasoning.
4054    #[cfg(feature = "verification")]
4055    fn try_oracle_fallback(&self, goal: &ProofGoal) -> ProofResult<Option<DerivationTree>> {
4056        crate::oracle::try_oracle(goal, &self.knowledge_base)
4057    }
4058}
4059
4060// =============================================================================
4061// HELPER FUNCTIONS
4062// =============================================================================
4063
4064/// Extract the type from an existential body if it contains type information.
4065///
4066/// Looks for TypedVar patterns in the body that might indicate the type
4067/// of the existentially quantified variable. Returns None if no type
4068/// information is found.
4069fn extract_type_from_exists_body(body: &ProofExpr) -> Option<String> {
4070    match body {
4071        // Direct TypedVar in body
4072        ProofExpr::TypedVar { typename, .. } => Some(typename.clone()),
4073
4074        // Recurse into conjunctions
4075        ProofExpr::And(l, r) => {
4076            extract_type_from_exists_body(l).or_else(|| extract_type_from_exists_body(r))
4077        }
4078
4079        // Recurse into disjunctions
4080        ProofExpr::Or(l, r) => {
4081            extract_type_from_exists_body(l).or_else(|| extract_type_from_exists_body(r))
4082        }
4083
4084        // Recurse into nested quantifiers
4085        ProofExpr::Exists { body, .. } | ProofExpr::ForAll { body, .. } => {
4086            extract_type_from_exists_body(body)
4087        }
4088
4089        // No type information found
4090        _ => None,
4091    }
4092}
4093
4094impl Default for BackwardChainer {
4095    fn default() -> Self {
4096        Self::new()
4097    }
4098}