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            ProofExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
2089                operator: operator.clone(),
2090                left: Box::new(self.simplify_definite_description_conjunction(left)),
2091                right: Box::new(self.simplify_definite_description_conjunction(right)),
2092            },
2093            _ => expr.clone(),
2094        }
2095    }
2096
2097    // =========================================================================
2098    // EVENT SEMANTICS ABSTRACTION
2099    // =========================================================================
2100
2101    /// Abstract Neo-Davidsonian event semantics to simple predicates.
2102    ///
2103    /// Converts: ∃e(Shave(e) ∧ Agent(e, x) ∧ Theme(e, y)) → shaves(x, y)
2104    ///
2105    /// This allows the proof engine to reason about events using simpler
2106    /// predicate logic, which is essential for paradoxes like the Barber Paradox.
2107    fn abstract_event_to_predicate(&self, expr: &ProofExpr) -> Option<ProofExpr> {
2108        match expr {
2109            // Direct NeoEvent abstraction
2110            ProofExpr::NeoEvent { verb, roles, .. } => {
2111                // Extract Agent and Theme/Patient roles
2112                let agent = roles.iter()
2113                    .find(|(role, _)| role == "Agent")
2114                    .map(|(_, term)| term.clone());
2115
2116                let theme = roles.iter()
2117                    .find(|(role, _)| role == "Theme" || role == "Patient")
2118                    .map(|(_, term)| term.clone());
2119
2120                // Build a simple predicate: verb(agent, theme) or verb(agent)
2121                let mut args = Vec::new();
2122                if let Some(a) = agent {
2123                    args.push(a);
2124                }
2125                if let Some(t) = theme {
2126                    args.push(t);
2127                }
2128
2129                // Lowercase the verb for predicate naming convention
2130                let pred_name = verb.to_lowercase();
2131
2132                Some(ProofExpr::Predicate {
2133                    name: pred_name,
2134                    args,
2135                    world: None,
2136                })
2137            }
2138
2139            // Handle Exists wrapping an event expression
2140            ProofExpr::Exists { variable, body } => {
2141                // Check if this is an event quantification
2142                if !self.is_event_variable(variable) {
2143                    return None;
2144                }
2145
2146                // Try direct NeoEvent abstraction
2147                if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2148                    return Some(abstracted);
2149                }
2150
2151                // Try to parse conjunction of event predicates
2152                // Pattern: ∃e(Verb(e) ∧ Agent(e, x) ∧ Theme(e, y)) → verb(x, y)
2153                if let Some(abstracted) = self.abstract_event_conjunction(variable, body) {
2154                    return Some(abstracted);
2155                }
2156
2157                None
2158            }
2159
2160            _ => None,
2161        }
2162    }
2163
2164    /// Abstract a conjunction of event predicates to a simple predicate.
2165    ///
2166    /// Handles: Verb(e) ∧ Agent(e, x) ∧ Theme(e, y) → verb(x, y)
2167    fn abstract_event_conjunction(&self, event_var: &str, body: &ProofExpr) -> Option<ProofExpr> {
2168        // Flatten the conjunction to get all components
2169        let mut components = Vec::new();
2170        self.flatten_conjunction(body, &mut components);
2171
2172        // Find verb predicate (single arg that matches event_var)
2173        let mut verb_name: Option<String> = None;
2174        let mut agent: Option<ProofTerm> = None;
2175        let mut theme: Option<ProofTerm> = None;
2176
2177        for comp in &components {
2178            if let ProofExpr::Predicate { name, args, .. } = comp {
2179                // Check if first arg is the event variable
2180                let first_is_event = args.first().map_or(false, |arg| {
2181                    matches!(arg, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == event_var)
2182                });
2183
2184                if !first_is_event && args.len() == 1 {
2185                    // Single arg predicate that's the event var
2186                    if let Some(ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v)) = args.first() {
2187                        if v == event_var {
2188                            verb_name = Some(name.clone());
2189                            continue;
2190                        }
2191                    }
2192                }
2193
2194                if first_is_event {
2195                    match name.as_str() {
2196                        "Agent" if args.len() == 2 => {
2197                            agent = Some(args[1].clone());
2198                        }
2199                        "Theme" | "Patient" if args.len() == 2 => {
2200                            theme = Some(args[1].clone());
2201                        }
2202                        _ if args.len() == 1 && verb_name.is_none() => {
2203                            // This is probably the verb predicate: Verb(e)
2204                            verb_name = Some(name.clone());
2205                        }
2206                        _ => {}
2207                    }
2208                }
2209            }
2210        }
2211
2212        // If we found a verb, construct the simple predicate
2213        if let Some(verb) = verb_name {
2214            let mut args = Vec::new();
2215            if let Some(a) = agent {
2216                args.push(a);
2217            }
2218            if let Some(t) = theme {
2219                args.push(t);
2220            }
2221
2222            return Some(ProofExpr::Predicate {
2223                name: verb.to_lowercase(),
2224                args,
2225                world: None,
2226            });
2227        }
2228
2229        None
2230    }
2231
2232    /// Check if a variable name looks like an event variable.
2233    ///
2234    /// Event variables are typically named "e", "e1", "e2", etc.
2235    fn is_event_variable(&self, var: &str) -> bool {
2236        var == "e" || var.starts_with("e_") ||
2237        (var.starts_with('e') && var.len() == 2 && var.chars().nth(1).map_or(false, |c| c.is_ascii_digit()))
2238    }
2239
2240    /// Recursively abstract all events in an expression.
2241    ///
2242    /// This transforms the entire expression tree, replacing event semantics
2243    /// with simple predicates wherever possible.
2244    fn abstract_all_events(&self, expr: &ProofExpr) -> ProofExpr {
2245        // First try direct abstraction
2246        if let Some(abstracted) = self.abstract_event_to_predicate(expr) {
2247            return abstracted;
2248        }
2249
2250        // Otherwise recurse into the structure
2251        match expr {
2252            ProofExpr::And(left, right) => ProofExpr::And(
2253                Box::new(self.abstract_all_events(left)),
2254                Box::new(self.abstract_all_events(right)),
2255            ),
2256            ProofExpr::Or(left, right) => ProofExpr::Or(
2257                Box::new(self.abstract_all_events(left)),
2258                Box::new(self.abstract_all_events(right)),
2259            ),
2260            ProofExpr::Implies(left, right) => ProofExpr::Implies(
2261                Box::new(self.abstract_all_events(left)),
2262                Box::new(self.abstract_all_events(right)),
2263            ),
2264            ProofExpr::Iff(left, right) => ProofExpr::Iff(
2265                Box::new(self.abstract_all_events(left)),
2266                Box::new(self.abstract_all_events(right)),
2267            ),
2268            ProofExpr::Not(inner) => {
2269                // Apply De Morgan for quantifiers: ¬∃x.P ≡ ∀x.¬P
2270                // This normalization is crucial for efficient proof search
2271                // (Converting negated existentials to universals helps the prover)
2272                if let ProofExpr::Exists { variable, body } = inner.as_ref() {
2273                    return ProofExpr::ForAll {
2274                        variable: variable.clone(),
2275                        body: Box::new(self.abstract_all_events(&ProofExpr::Not(body.clone()))),
2276                    };
2277                }
2278                // Note: We do NOT convert ¬∀x.P to ∃x.¬P because the prover
2279                // works better with universal quantifiers for backward chaining.
2280                ProofExpr::Not(Box::new(self.abstract_all_events(inner)))
2281            }
2282            ProofExpr::ForAll { variable, body } => {
2283                // Check for pattern: ∀x ¬(P ∧ Q) → ∀x (P → ¬Q)
2284                // This converts to implication form for better backward chaining
2285                if let ProofExpr::Not(inner) = body.as_ref() {
2286                    if let ProofExpr::And(left, right) = inner.as_ref() {
2287                        return ProofExpr::ForAll {
2288                            variable: variable.clone(),
2289                            body: Box::new(ProofExpr::Implies(
2290                                Box::new(self.abstract_all_events(left)),
2291                                Box::new(self.abstract_all_events(&ProofExpr::Not(right.clone()))),
2292                            )),
2293                        };
2294                    }
2295                }
2296                ProofExpr::ForAll {
2297                    variable: variable.clone(),
2298                    body: Box::new(self.abstract_all_events(body)),
2299                }
2300            }
2301            ProofExpr::Exists { variable, body } => {
2302                // Check if this is an event quantification that should be abstracted
2303                if self.is_event_variable(variable) {
2304                    if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2305                        return abstracted;
2306                    }
2307                }
2308                // Otherwise keep the existential and recurse
2309                ProofExpr::Exists {
2310                    variable: variable.clone(),
2311                    body: Box::new(self.abstract_all_events(body)),
2312                }
2313            }
2314            // For other expressions, return as-is
2315            other => other.clone(),
2316        }
2317    }
2318
2319    /// Abstract event semantics WITHOUT applying De Morgan transformations.
2320    ///
2321    /// This is used for goals where we want to preserve the ¬∃ pattern
2322    /// for reductio ad absurdum strategies.
2323    fn abstract_events_only(&self, expr: &ProofExpr) -> ProofExpr {
2324        // First try direct abstraction
2325        if let Some(abstracted) = self.abstract_event_to_predicate(expr) {
2326            return abstracted;
2327        }
2328
2329        // Otherwise recurse into the structure
2330        match expr {
2331            ProofExpr::And(left, right) => ProofExpr::And(
2332                Box::new(self.abstract_events_only(left)),
2333                Box::new(self.abstract_events_only(right)),
2334            ),
2335            ProofExpr::Or(left, right) => ProofExpr::Or(
2336                Box::new(self.abstract_events_only(left)),
2337                Box::new(self.abstract_events_only(right)),
2338            ),
2339            ProofExpr::Implies(left, right) => ProofExpr::Implies(
2340                Box::new(self.abstract_events_only(left)),
2341                Box::new(self.abstract_events_only(right)),
2342            ),
2343            ProofExpr::Iff(left, right) => ProofExpr::Iff(
2344                Box::new(self.abstract_events_only(left)),
2345                Box::new(self.abstract_events_only(right)),
2346            ),
2347            ProofExpr::Not(inner) => {
2348                // Just recurse, no De Morgan transformation
2349                ProofExpr::Not(Box::new(self.abstract_events_only(inner)))
2350            }
2351            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
2352                variable: variable.clone(),
2353                body: Box::new(self.abstract_events_only(body)),
2354            },
2355            ProofExpr::Exists { variable, body } => {
2356                // Check if this is an event quantification that should be abstracted
2357                if self.is_event_variable(variable) {
2358                    if let Some(abstracted) = self.abstract_event_to_predicate(body) {
2359                        return abstracted;
2360                    }
2361                }
2362                // Otherwise keep the existential and recurse
2363                ProofExpr::Exists {
2364                    variable: variable.clone(),
2365                    body: Box::new(self.abstract_events_only(body)),
2366                }
2367            }
2368            // For other expressions, return as-is
2369            other => other.clone(),
2370        }
2371    }
2372
2373    /// Look for a contradiction in the knowledge base and context.
2374    ///
2375    /// A contradiction exists when both P and ¬P are derivable.
2376    fn find_contradiction(
2377        &mut self,
2378        context: &[ProofExpr],
2379        depth: usize,
2380    ) -> ProofResult<Option<DerivationTree>> {
2381        // Collect all expressions from KB and context
2382        let all_exprs: Vec<ProofExpr> = self.knowledge_base.iter()
2383            .chain(context.iter())
2384            .cloned()
2385            .collect();
2386
2387        // Strategy 1: Look for direct P and ¬P pairs
2388        for expr in &all_exprs {
2389            if let ProofExpr::Not(inner) = expr {
2390                // We have ¬P, check if P exists directly
2391                for other in &all_exprs {
2392                    if exprs_structurally_equal(other, inner) {
2393                        // Found both P and ¬P directly
2394                        let pos_leaf = DerivationTree::leaf(
2395                            (**inner).clone(),
2396                            InferenceRule::PremiseMatch,
2397                        );
2398                        let neg_leaf = DerivationTree::leaf(
2399                            expr.clone(),
2400                            InferenceRule::PremiseMatch,
2401                        );
2402                        return Ok(Some(DerivationTree::new(
2403                            ProofExpr::Atom("⊥".into()),
2404                            InferenceRule::Contradiction,
2405                            vec![pos_leaf, neg_leaf],
2406                        )));
2407                    }
2408                }
2409            }
2410        }
2411
2412        // Strategy 2: Look for implications that derive contradictory results
2413        // Check if context fact P triggers P → ¬P (immediate contradiction)
2414        // Or if P triggers P → Q where ¬Q is also in context
2415        // Note: Extract implications from both top-level and inside ForAll quantifiers
2416        let mut implications: Vec<(ProofExpr, ProofExpr)> = Vec::new();
2417        for e in &all_exprs {
2418            if let ProofExpr::Implies(ante, cons) = e {
2419                implications.push(((**ante).clone(), (**cons).clone()));
2420            }
2421            // Also extract from inside ForAll (important for barber paradox!)
2422            if let ProofExpr::ForAll { body, .. } = e {
2423                if let ProofExpr::Implies(ante, cons) = body.as_ref() {
2424                    implications.push(((**ante).clone(), (**cons).clone()));
2425                }
2426            }
2427        }
2428
2429        // For each fact in the context, see if it triggers contradictory implications
2430        for fact in context {
2431            // Find all implications where fact matches the antecedent
2432            let mut derivable_consequences: Vec<ProofExpr> = Vec::new();
2433
2434            for (ante, cons) in &implications {
2435                // Try to unify the antecedent with the fact
2436                if let Ok(subst) = unify_exprs(fact, ante) {
2437                    let instantiated_cons = apply_subst_to_expr(cons, &subst);
2438                    derivable_consequences.push(instantiated_cons);
2439                }
2440
2441                // Also try matching conjunctive antecedents with multiple facts
2442                if let ProofExpr::And(left, right) = ante {
2443                    // Try to find facts matching both parts of the conjunction
2444                    if let Some(subst) = self.try_match_conjunction_antecedent(
2445                        left, right, &all_exprs
2446                    ) {
2447                        let instantiated_cons = apply_subst_to_expr(cons, &subst);
2448                        if !derivable_consequences.contains(&instantiated_cons) {
2449                            derivable_consequences.push(instantiated_cons);
2450                        }
2451                    }
2452                }
2453            }
2454
2455            // Check if any derived consequence contradicts the triggering fact
2456            for cons in &derivable_consequences {
2457                // Check if cons = ¬fact (the classic barber structure: P → ¬P)
2458                if let ProofExpr::Not(inner) = cons {
2459                    if exprs_structurally_equal(inner, fact) {
2460                        // fact triggered an implication that derives ¬fact
2461                        // This is a contradiction: fact ∧ ¬fact
2462                        let pos_leaf = DerivationTree::leaf(
2463                            fact.clone(),
2464                            InferenceRule::PremiseMatch,
2465                        );
2466                        let neg_leaf = DerivationTree::leaf(
2467                            cons.clone(),
2468                            InferenceRule::ModusPonens,
2469                        );
2470                        return Ok(Some(DerivationTree::new(
2471                            ProofExpr::Atom("⊥".into()),
2472                            InferenceRule::Contradiction,
2473                            vec![pos_leaf, neg_leaf],
2474                        )));
2475                    }
2476                }
2477
2478                // Check if cons contradicts any other fact in context
2479                for other in context {
2480                    if std::ptr::eq(fact as *const _, other as *const _) {
2481                        continue; // Skip the triggering fact itself
2482                    }
2483                    // Check if cons = ¬other
2484                    if let ProofExpr::Not(inner) = cons {
2485                        if exprs_structurally_equal(inner, other) {
2486                            let pos_leaf = DerivationTree::leaf(
2487                                other.clone(),
2488                                InferenceRule::PremiseMatch,
2489                            );
2490                            let neg_leaf = DerivationTree::leaf(
2491                                cons.clone(),
2492                                InferenceRule::ModusPonens,
2493                            );
2494                            return Ok(Some(DerivationTree::new(
2495                                ProofExpr::Atom("⊥".into()),
2496                                InferenceRule::Contradiction,
2497                                vec![pos_leaf, neg_leaf],
2498                            )));
2499                        }
2500                    }
2501                    // Check if other = ¬cons
2502                    if let ProofExpr::Not(inner_other) = other {
2503                        if exprs_structurally_equal(inner_other, cons) {
2504                            let pos_leaf = DerivationTree::leaf(
2505                                cons.clone(),
2506                                InferenceRule::ModusPonens,
2507                            );
2508                            let neg_leaf = DerivationTree::leaf(
2509                                other.clone(),
2510                                InferenceRule::PremiseMatch,
2511                            );
2512                            return Ok(Some(DerivationTree::new(
2513                                ProofExpr::Atom("⊥".into()),
2514                                InferenceRule::Contradiction,
2515                                vec![pos_leaf, neg_leaf],
2516                            )));
2517                        }
2518                    }
2519                }
2520            }
2521
2522            // Check if any pair of consequences contradicts each other
2523            for i in 0..derivable_consequences.len() {
2524                for j in (i + 1)..derivable_consequences.len() {
2525                    let cons1 = &derivable_consequences[i];
2526                    let cons2 = &derivable_consequences[j];
2527
2528                    // Check if cons1 = ¬cons2 or cons2 = ¬cons1
2529                    if let ProofExpr::Not(inner1) = cons1 {
2530                        if exprs_structurally_equal(inner1, cons2) {
2531                            // cons1 = ¬cons2, contradiction!
2532                            let pos_leaf = DerivationTree::leaf(
2533                                cons2.clone(),
2534                                InferenceRule::ModusPonens,
2535                            );
2536                            let neg_leaf = DerivationTree::leaf(
2537                                cons1.clone(),
2538                                InferenceRule::ModusPonens,
2539                            );
2540                            return Ok(Some(DerivationTree::new(
2541                                ProofExpr::Atom("⊥".into()),
2542                                InferenceRule::Contradiction,
2543                                vec![pos_leaf, neg_leaf],
2544                            )));
2545                        }
2546                    }
2547                    if let ProofExpr::Not(inner2) = cons2 {
2548                        if exprs_structurally_equal(inner2, cons1) {
2549                            // cons2 = ¬cons1, contradiction!
2550                            let pos_leaf = DerivationTree::leaf(
2551                                cons1.clone(),
2552                                InferenceRule::ModusPonens,
2553                            );
2554                            let neg_leaf = DerivationTree::leaf(
2555                                cons2.clone(),
2556                                InferenceRule::ModusPonens,
2557                            );
2558                            return Ok(Some(DerivationTree::new(
2559                                ProofExpr::Atom("⊥".into()),
2560                                InferenceRule::Contradiction,
2561                                vec![pos_leaf, neg_leaf],
2562                            )));
2563                        }
2564                    }
2565                }
2566            }
2567        }
2568
2569        // Strategy 3: Try to find self-referential contradictions (like Barber Paradox)
2570        if let Some(proof) = self.find_self_referential_contradiction(context, depth)? {
2571            return Ok(Some(proof));
2572        }
2573
2574        Ok(None)
2575    }
2576
2577    /// Try to match a conjunctive antecedent with facts in the context.
2578    ///
2579    /// For an antecedent like (man(z) ∧ shave(z,z)), we need to find facts
2580    /// that match both parts with consistent variable bindings.
2581    fn try_match_conjunction_antecedent(
2582        &self,
2583        left: &ProofExpr,
2584        right: &ProofExpr,
2585        facts: &[ProofExpr],
2586    ) -> Option<Substitution> {
2587        // Try to find a fact that matches the left part
2588        for fact1 in facts {
2589            if let Ok(subst1) = unify_exprs(fact1, left) {
2590                // Apply this substitution to the right part
2591                let instantiated_right = apply_subst_to_expr(right, &subst1);
2592                // Now look for a fact that matches the instantiated right part
2593                for fact2 in facts {
2594                    if let Ok(subst2) = unify_exprs(fact2, &instantiated_right) {
2595                        // Combine substitutions
2596                        let mut combined = subst1.clone();
2597                        for (k, v) in subst2.iter() {
2598                            combined.insert(k.clone(), v.clone());
2599                        }
2600                        return Some(combined);
2601                    }
2602                }
2603            }
2604        }
2605        // Also try right then left
2606        for fact1 in facts {
2607            if let Ok(subst1) = unify_exprs(fact1, right) {
2608                let instantiated_left = apply_subst_to_expr(left, &subst1);
2609                for fact2 in facts {
2610                    if let Ok(subst2) = unify_exprs(fact2, &instantiated_left) {
2611                        let mut combined = subst1.clone();
2612                        for (k, v) in subst2.iter() {
2613                            combined.insert(k.clone(), v.clone());
2614                        }
2615                        return Some(combined);
2616                    }
2617                }
2618            }
2619        }
2620        None
2621    }
2622
2623    /// Special case: find self-referential contradictions (like the Barber Paradox).
2624    ///
2625    /// Pattern: If we have ∀x(P(x) → Q(b, x)) and ∀x(P(x) → ¬Q(b, x)),
2626    /// then for x = b with P(b), we get Q(b, b) ∧ ¬Q(b, b).
2627    ///
2628    /// This uses direct pattern matching WITHOUT recursive prove_goal calls
2629    /// to avoid infinite recursion.
2630    fn find_self_referential_contradiction(
2631        &mut self,
2632        context: &[ProofExpr],
2633        _depth: usize,
2634    ) -> ProofResult<Option<DerivationTree>> {
2635        // Collect all expressions from KB and context
2636        let all_exprs: Vec<ProofExpr> = self.knowledge_base.iter()
2637            .chain(context.iter())
2638            .cloned()
2639            .collect();
2640
2641        // Look for pairs of universal implications with contradictory conclusions
2642        // that can be instantiated with the same witness
2643        for expr1 in &all_exprs {
2644            if let ProofExpr::ForAll { variable: var1, body: body1 } = expr1 {
2645                if let ProofExpr::Implies(ante1, cons1) = body1.as_ref() {
2646                    for expr2 in &all_exprs {
2647                        if std::ptr::eq(expr1, expr2) {
2648                            continue; // Skip same expression
2649                        }
2650                        if let ProofExpr::ForAll { variable: var2, body: body2 } = expr2 {
2651                            if let ProofExpr::Implies(ante2, cons2) = body2.as_ref() {
2652                                // Check if cons2 = ¬cons1 (structurally)
2653                                if let ProofExpr::Not(neg_cons2) = cons2.as_ref() {
2654                                    // Check if cons1 and neg_cons2 have matching structure
2655                                    // For barber: cons1 = shaves(barber, x), neg_cons2 = shaves(barber, x)
2656
2657                                    // Try instantiating with x = barber (the self-referential case)
2658                                    // We look for constant terms in cons1 that could be witnesses
2659                                    let witnesses = self.extract_constants_from_expr(cons1);
2660
2661                                    for witness_name in &witnesses {
2662                                        let witness = ProofTerm::Constant(witness_name.clone());
2663
2664                                        // Instantiate both antecedents and consequents with this witness
2665                                        let mut subst1 = Substitution::new();
2666                                        subst1.insert(var1.clone(), witness.clone());
2667                                        let ante1_inst = apply_subst_to_expr(ante1, &subst1);
2668                                        let cons1_inst = apply_subst_to_expr(cons1, &subst1);
2669
2670                                        let mut subst2 = Substitution::new();
2671                                        subst2.insert(var2.clone(), witness.clone());
2672                                        let ante2_inst = apply_subst_to_expr(ante2, &subst2);
2673                                        let cons2_inst = apply_subst_to_expr(cons2, &subst2);
2674
2675                                        // Check if cons1_inst and ¬cons2_inst contradict
2676                                        // cons2_inst should be ¬X where X = cons1_inst
2677                                        if let ProofExpr::Not(inner2) = &cons2_inst {
2678                                            if exprs_structurally_equal(&cons1_inst, inner2) {
2679                                                // Now check if both antecedents could hold
2680                                                // ante1 typically is ¬P(x,x) and ante2 is P(x,x)
2681                                                // These are complementary - one must hold
2682                                                // For the paradox, we consider BOTH cases
2683
2684                                                // If ante1 = ¬P(x,x) and ante2 = P(x,x), and x = witness,
2685                                                // we have a tertium non datur case:
2686                                                // - Either P(w,w) holds → cons2_inst = ¬cons1_inst
2687                                                // - Or ¬P(w,w) holds → cons1_inst
2688
2689                                                // Check if ante1 and ante2 are complements
2690                                                if self.are_complements(&ante1_inst, &ante2_inst) {
2691                                                    // By excluded middle, one antecedent holds
2692                                                    // If cons1_inst and cons2_inst = ¬cons1_inst,
2693                                                    // we have a contradiction
2694                                                    let pos_leaf = DerivationTree::leaf(
2695                                                        cons1_inst.clone(),
2696                                                        InferenceRule::ModusPonens,
2697                                                    );
2698                                                    let neg_leaf = DerivationTree::leaf(
2699                                                        cons2_inst,
2700                                                        InferenceRule::ModusPonens,
2701                                                    );
2702                                                    return Ok(Some(DerivationTree::new(
2703                                                        ProofExpr::Atom("⊥".into()),
2704                                                        InferenceRule::Contradiction,
2705                                                        vec![pos_leaf, neg_leaf],
2706                                                    )));
2707                                                }
2708                                            }
2709                                        }
2710                                    }
2711                                }
2712                            }
2713                        }
2714                    }
2715                }
2716            }
2717        }
2718
2719        Ok(None)
2720    }
2721
2722    /// Check if two expressions are complements (one is the negation of the other).
2723    fn are_complements(&self, expr1: &ProofExpr, expr2: &ProofExpr) -> bool {
2724        // Check if expr1 = ¬expr2
2725        if let ProofExpr::Not(inner1) = expr1 {
2726            if exprs_structurally_equal(inner1, expr2) {
2727                return true;
2728            }
2729        }
2730        // Check if expr2 = ¬expr1
2731        if let ProofExpr::Not(inner2) = expr2 {
2732            if exprs_structurally_equal(inner2, expr1) {
2733                return true;
2734            }
2735        }
2736        false
2737    }
2738
2739    /// Extract constant names from an expression.
2740    fn extract_constants_from_expr(&self, expr: &ProofExpr) -> Vec<String> {
2741        let mut constants = Vec::new();
2742        self.extract_constants_recursive(expr, &mut constants);
2743        constants
2744    }
2745
2746    fn extract_constants_recursive(&self, expr: &ProofExpr, constants: &mut Vec<String>) {
2747        match expr {
2748            ProofExpr::Predicate { args, .. } => {
2749                for arg in args {
2750                    self.extract_constants_from_term_recursive(arg, constants);
2751                }
2752            }
2753            ProofExpr::Identity(l, r) => {
2754                self.extract_constants_from_term_recursive(l, constants);
2755                self.extract_constants_from_term_recursive(r, constants);
2756            }
2757            ProofExpr::And(l, r)
2758            | ProofExpr::Or(l, r)
2759            | ProofExpr::Implies(l, r)
2760            | ProofExpr::Iff(l, r) => {
2761                self.extract_constants_recursive(l, constants);
2762                self.extract_constants_recursive(r, constants);
2763            }
2764            ProofExpr::Not(inner) => {
2765                self.extract_constants_recursive(inner, constants);
2766            }
2767            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
2768                self.extract_constants_recursive(body, constants);
2769            }
2770            _ => {}
2771        }
2772    }
2773
2774    fn extract_constants_from_term_recursive(&self, term: &ProofTerm, constants: &mut Vec<String>) {
2775        match term {
2776            ProofTerm::Constant(name) => {
2777                if !constants.contains(name) {
2778                    constants.push(name.clone());
2779                }
2780            }
2781            ProofTerm::Function(_, args) => {
2782                for arg in args {
2783                    self.extract_constants_from_term_recursive(arg, constants);
2784                }
2785            }
2786            ProofTerm::Group(terms) => {
2787                for t in terms {
2788                    self.extract_constants_from_term_recursive(t, constants);
2789                }
2790            }
2791            _ => {}
2792        }
2793    }
2794
2795    // =========================================================================
2796    // STRATEGY 6: EQUALITY REWRITING (LEIBNIZ'S LAW)
2797    // =========================================================================
2798
2799    /// Try rewriting using equalities in the knowledge base.
2800    ///
2801    /// Leibniz's Law: If a = b and P(a), then P(b).
2802    /// Also handles symmetry (a = b ⊢ b = a) and transitivity (a = b, b = c ⊢ a = c).
2803    fn try_equality_rewrite(
2804        &mut self,
2805        goal: &ProofGoal,
2806        depth: usize,
2807    ) -> ProofResult<Option<DerivationTree>> {
2808        // Collect equalities from KB and context
2809        let equalities: Vec<(ProofTerm, ProofTerm)> = self
2810            .knowledge_base
2811            .iter()
2812            .chain(goal.context.iter())
2813            .filter_map(|expr| {
2814                if let ProofExpr::Identity(l, r) = expr {
2815                    Some((l.clone(), r.clone()))
2816                } else {
2817                    None
2818                }
2819            })
2820            .collect();
2821
2822        if equalities.is_empty() {
2823            return Ok(None);
2824        }
2825
2826        // Handle special case: goal is itself an equality (symmetry/transitivity)
2827        if let ProofExpr::Identity(goal_l, goal_r) = &goal.target {
2828            // Try symmetry: a = b ⊢ b = a
2829            if let Some(tree) = self.try_equality_symmetry(goal_l, goal_r, &equalities, depth)? {
2830                return Ok(Some(tree));
2831            }
2832
2833            // Try transitivity: a = b, b = c ⊢ a = c
2834            if let Some(tree) = self.try_equality_transitivity(goal_l, goal_r, &equalities, depth)? {
2835                return Ok(Some(tree));
2836            }
2837
2838            // Try equational rewriting: use axioms to rewrite LHS step by step
2839            // Only if we have depth budget remaining (prevents infinite recursion)
2840            if depth + 3 < self.max_depth {
2841                if let Some(tree) = self.try_equational_identity_rewrite(goal, goal_l, goal_r, depth)? {
2842                    return Ok(Some(tree));
2843                }
2844            }
2845
2846            return Ok(None);
2847        }
2848
2849        // Try rewriting: substitute one term for another (for non-Identity goals)
2850        for (eq_from, eq_to) in &equalities {
2851            // Try forward: a = b, P(a) ⊢ P(b)
2852            if let Some(tree) = self.try_rewrite_with_equality(
2853                goal, eq_from, eq_to, depth,
2854            )? {
2855                return Ok(Some(tree));
2856            }
2857
2858            // Try backward: a = b, P(b) ⊢ P(a)
2859            if let Some(tree) = self.try_rewrite_with_equality(
2860                goal, eq_to, eq_from, depth,
2861            )? {
2862                return Ok(Some(tree));
2863            }
2864        }
2865
2866        Ok(None)
2867    }
2868
2869    /// Try to prove goal by substituting `from` with `to` in some known fact.
2870    fn try_rewrite_with_equality(
2871        &mut self,
2872        goal: &ProofGoal,
2873        from: &ProofTerm,
2874        to: &ProofTerm,
2875        depth: usize,
2876    ) -> ProofResult<Option<DerivationTree>> {
2877        // Create the "source" expression by substituting `to` with `from` in the goal
2878        // If goal is P(b) and we have a = b, we want to find P(a)
2879        let source_goal = self.substitute_term_in_expr(&goal.target, to, from);
2880
2881        // Check if source_goal differs from the goal (substitution had effect)
2882        if source_goal == goal.target {
2883            return Ok(None);
2884        }
2885
2886        // Try to prove the source goal
2887        let source_proof_goal = ProofGoal::with_context(source_goal.clone(), goal.context.clone());
2888        if let Ok(source_proof) = self.prove_goal(source_proof_goal, depth + 1) {
2889            // Also need a proof of the equality
2890            let equality = ProofExpr::Identity(from.clone(), to.clone());
2891            let eq_proof_goal = ProofGoal::with_context(equality.clone(), goal.context.clone());
2892
2893            if let Ok(eq_proof) = self.prove_goal(eq_proof_goal, depth + 1) {
2894                return Ok(Some(DerivationTree::new(
2895                    goal.target.clone(),
2896                    InferenceRule::Rewrite {
2897                        from: from.clone(),
2898                        to: to.clone(),
2899                    },
2900                    vec![eq_proof, source_proof],
2901                )));
2902            }
2903        }
2904
2905        Ok(None)
2906    }
2907
2908    /// Try equality symmetry: a = b ⊢ b = a
2909    fn try_equality_symmetry(
2910        &mut self,
2911        goal_l: &ProofTerm,
2912        goal_r: &ProofTerm,
2913        equalities: &[(ProofTerm, ProofTerm)],
2914        _depth: usize,
2915    ) -> ProofResult<Option<DerivationTree>> {
2916        // Check if we have r = l in KB (so we can derive l = r)
2917        for (eq_l, eq_r) in equalities {
2918            if eq_l == goal_r && eq_r == goal_l {
2919                // Found r = l, can derive l = r by symmetry
2920                let source = ProofExpr::Identity(goal_r.clone(), goal_l.clone());
2921                return Ok(Some(DerivationTree::new(
2922                    ProofExpr::Identity(goal_l.clone(), goal_r.clone()),
2923                    InferenceRule::EqualitySymmetry,
2924                    vec![DerivationTree::leaf(source, InferenceRule::PremiseMatch)],
2925                )));
2926            }
2927        }
2928        Ok(None)
2929    }
2930
2931    /// Try equality transitivity: a = b, b = c ⊢ a = c
2932    fn try_equality_transitivity(
2933        &mut self,
2934        goal_l: &ProofTerm,
2935        goal_r: &ProofTerm,
2936        equalities: &[(ProofTerm, ProofTerm)],
2937        _depth: usize,
2938    ) -> ProofResult<Option<DerivationTree>> {
2939        // Look for a = b and b = c where we want a = c
2940        for (eq1_l, eq1_r) in equalities {
2941            if eq1_l == goal_l {
2942                // Found a = b, now look for b = c
2943                for (eq2_l, eq2_r) in equalities {
2944                    if eq2_l == eq1_r && eq2_r == goal_r {
2945                        // Found a = b and b = c, derive a = c
2946                        let premise1 = ProofExpr::Identity(eq1_l.clone(), eq1_r.clone());
2947                        let premise2 = ProofExpr::Identity(eq2_l.clone(), eq2_r.clone());
2948                        return Ok(Some(DerivationTree::new(
2949                            ProofExpr::Identity(goal_l.clone(), goal_r.clone()),
2950                            InferenceRule::EqualityTransitivity,
2951                            vec![
2952                                DerivationTree::leaf(premise1, InferenceRule::PremiseMatch),
2953                                DerivationTree::leaf(premise2, InferenceRule::PremiseMatch),
2954                            ],
2955                        )));
2956                    }
2957                }
2958            }
2959        }
2960        Ok(None)
2961    }
2962
2963    /// Try equational rewriting for Identity goals.
2964    ///
2965    /// For a goal `f(a) = b`, find an axiom `f(x) = g(x)` that matches,
2966    /// rewrite to get `g(a) = b`, and recursively prove that.
2967    fn try_equational_identity_rewrite(
2968        &mut self,
2969        goal: &ProofGoal,
2970        goal_l: &ProofTerm,
2971        goal_r: &ProofTerm,
2972        depth: usize,
2973    ) -> ProofResult<Option<DerivationTree>> {
2974        // First, try congruence: if both sides have the same outermost function/ctor,
2975        // recursively prove the arguments are equal.
2976        if let (
2977            ProofTerm::Function(name_l, args_l),
2978            ProofTerm::Function(name_r, args_r),
2979        ) = (goal_l, goal_r)
2980        {
2981            if name_l == name_r && args_l.len() == args_r.len() {
2982                // All arguments must be equal
2983                let mut arg_proofs = Vec::new();
2984                let mut all_ok = true;
2985                for (arg_l, arg_r) in args_l.iter().zip(args_r.iter()) {
2986                    let arg_goal_expr = ProofExpr::Identity(arg_l.clone(), arg_r.clone());
2987                    let arg_goal = ProofGoal::with_context(arg_goal_expr, goal.context.clone());
2988                    match self.prove_goal(arg_goal, depth + 1) {
2989                        Ok(proof) => arg_proofs.push(proof),
2990                        Err(_) => {
2991                            all_ok = false;
2992                            break;
2993                        }
2994                    }
2995                }
2996                if all_ok {
2997                    // All arguments are equal, so the functions are equal by congruence
2998                    return Ok(Some(DerivationTree::new(
2999                        goal.target.clone(),
3000                        InferenceRule::Reflexivity, // Using Reflexivity for congruence
3001                        arg_proofs,
3002                    )));
3003                }
3004            }
3005        }
3006        // Collect Identity axioms from KB
3007        let axioms: Vec<(ProofTerm, ProofTerm)> = self
3008            .knowledge_base
3009            .iter()
3010            .filter_map(|e| {
3011                if let ProofExpr::Identity(l, r) = e {
3012                    Some((l.clone(), r.clone()))
3013                } else {
3014                    None
3015                }
3016            })
3017            .collect();
3018
3019        // Try each axiom to rewrite the goal's LHS
3020        for (axiom_l, axiom_r) in &axioms {
3021            // Rename variables in axiom to avoid capture (use same map for both sides!)
3022            let mut var_map = std::collections::HashMap::new();
3023            let renamed_l = self.rename_term_vars_with_map(axiom_l, &mut var_map);
3024            let renamed_r = self.rename_term_vars_with_map(axiom_r, &mut var_map);
3025
3026            // Try to unify axiom LHS with goal LHS
3027            // e.g., unify(Add(Succ(k), n), Add(Succ(Zero), Succ(Zero)))
3028            //       => {k: Zero, n: Succ(Zero)}
3029            if let Ok(subst) = unify_terms(&renamed_l, goal_l) {
3030                // Apply substitution to axiom RHS to get the rewritten term
3031                let rewritten = self.apply_subst_to_term(&renamed_r, &subst);
3032
3033                // First check: does rewritten equal goal_r directly?
3034                if terms_structurally_equal(&rewritten, goal_r) {
3035                    // Direct match! Build the proof
3036                    let axiom_expr = ProofExpr::Identity(axiom_l.clone(), axiom_r.clone());
3037                    return Ok(Some(DerivationTree::new(
3038                        goal.target.clone(),
3039                        InferenceRule::Rewrite {
3040                            from: goal_l.clone(),
3041                            to: rewritten,
3042                        },
3043                        vec![DerivationTree::leaf(axiom_expr, InferenceRule::PremiseMatch)],
3044                    )));
3045                }
3046
3047                // Otherwise, create a new goal with the rewritten LHS
3048                let new_goal_expr = ProofExpr::Identity(rewritten.clone(), goal_r.clone());
3049                let new_goal = ProofGoal::with_context(new_goal_expr.clone(), goal.context.clone());
3050
3051                // Recursively try to prove the new goal
3052                if let Ok(sub_proof) = self.prove_goal(new_goal, depth + 1) {
3053                    // Success! Build the full proof
3054                    let axiom_expr = ProofExpr::Identity(axiom_l.clone(), axiom_r.clone());
3055                    return Ok(Some(DerivationTree::new(
3056                        goal.target.clone(),
3057                        InferenceRule::Rewrite {
3058                            from: goal_l.clone(),
3059                            to: rewritten,
3060                        },
3061                        vec![
3062                            DerivationTree::leaf(axiom_expr, InferenceRule::PremiseMatch),
3063                            sub_proof,
3064                        ],
3065                    )));
3066                }
3067            }
3068        }
3069
3070        Ok(None)
3071    }
3072
3073    /// Rename variables in a term to fresh names (consistently).
3074    fn rename_term_vars(&mut self, term: &ProofTerm) -> ProofTerm {
3075        let mut var_map = std::collections::HashMap::new();
3076        self.rename_term_vars_with_map(term, &mut var_map)
3077    }
3078
3079    fn rename_term_vars_with_map(
3080        &mut self,
3081        term: &ProofTerm,
3082        var_map: &mut std::collections::HashMap<String, String>,
3083    ) -> ProofTerm {
3084        match term {
3085            ProofTerm::Variable(name) => {
3086                // Check if we've already renamed this variable
3087                if let Some(fresh) = var_map.get(name) {
3088                    ProofTerm::Variable(fresh.clone())
3089                } else {
3090                    // Create fresh name and remember it
3091                    let fresh = format!("_v{}", self.var_counter);
3092                    self.var_counter += 1;
3093                    var_map.insert(name.clone(), fresh.clone());
3094                    ProofTerm::Variable(fresh)
3095                }
3096            }
3097            ProofTerm::Function(name, args) => {
3098                ProofTerm::Function(
3099                    name.clone(),
3100                    args.iter().map(|a| self.rename_term_vars_with_map(a, var_map)).collect(),
3101                )
3102            }
3103            ProofTerm::Group(terms) => {
3104                ProofTerm::Group(
3105                    terms.iter().map(|t| self.rename_term_vars_with_map(t, var_map)).collect(),
3106                )
3107            }
3108            other => other.clone(),
3109        }
3110    }
3111
3112    /// Apply a substitution to a term.
3113    fn apply_subst_to_term(&self, term: &ProofTerm, subst: &Substitution) -> ProofTerm {
3114        match term {
3115            ProofTerm::Variable(name) => {
3116                if let Some(replacement) = subst.get(name) {
3117                    replacement.clone()
3118                } else {
3119                    term.clone()
3120                }
3121            }
3122            ProofTerm::Function(name, args) => {
3123                ProofTerm::Function(
3124                    name.clone(),
3125                    args.iter().map(|a| self.apply_subst_to_term(a, subst)).collect(),
3126                )
3127            }
3128            ProofTerm::Group(terms) => {
3129                ProofTerm::Group(terms.iter().map(|t| self.apply_subst_to_term(t, subst)).collect())
3130            }
3131            other => other.clone(),
3132        }
3133    }
3134
3135    /// Substitute a term for another in an expression.
3136    fn substitute_term_in_expr(
3137        &self,
3138        expr: &ProofExpr,
3139        from: &ProofTerm,
3140        to: &ProofTerm,
3141    ) -> ProofExpr {
3142        match expr {
3143            ProofExpr::Predicate { name, args, world } => {
3144                let new_args: Vec<_> = args
3145                    .iter()
3146                    .map(|arg| self.substitute_in_term(arg, from, to))
3147                    .collect();
3148                ProofExpr::Predicate {
3149                    name: name.clone(),
3150                    args: new_args,
3151                    world: world.clone(),
3152                }
3153            }
3154            ProofExpr::Identity(l, r) => ProofExpr::Identity(
3155                self.substitute_in_term(l, from, to),
3156                self.substitute_in_term(r, from, to),
3157            ),
3158            ProofExpr::And(l, r) => ProofExpr::And(
3159                Box::new(self.substitute_term_in_expr(l, from, to)),
3160                Box::new(self.substitute_term_in_expr(r, from, to)),
3161            ),
3162            ProofExpr::Or(l, r) => ProofExpr::Or(
3163                Box::new(self.substitute_term_in_expr(l, from, to)),
3164                Box::new(self.substitute_term_in_expr(r, from, to)),
3165            ),
3166            ProofExpr::Implies(l, r) => ProofExpr::Implies(
3167                Box::new(self.substitute_term_in_expr(l, from, to)),
3168                Box::new(self.substitute_term_in_expr(r, from, to)),
3169            ),
3170            ProofExpr::Not(inner) => {
3171                ProofExpr::Not(Box::new(self.substitute_term_in_expr(inner, from, to)))
3172            }
3173            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
3174                variable: variable.clone(),
3175                body: Box::new(self.substitute_term_in_expr(body, from, to)),
3176            },
3177            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
3178                variable: variable.clone(),
3179                body: Box::new(self.substitute_term_in_expr(body, from, to)),
3180            },
3181            // For other expressions, return as-is
3182            other => other.clone(),
3183        }
3184    }
3185
3186    /// Substitute a term for another in a ProofTerm.
3187    fn substitute_in_term(
3188        &self,
3189        term: &ProofTerm,
3190        from: &ProofTerm,
3191        to: &ProofTerm,
3192    ) -> ProofTerm {
3193        if term == from {
3194            return to.clone();
3195        }
3196        match term {
3197            ProofTerm::Function(name, args) => {
3198                let new_args: Vec<_> = args
3199                    .iter()
3200                    .map(|arg| self.substitute_in_term(arg, from, to))
3201                    .collect();
3202                ProofTerm::Function(name.clone(), new_args)
3203            }
3204            ProofTerm::Group(terms) => {
3205                let new_terms: Vec<_> = terms
3206                    .iter()
3207                    .map(|t| self.substitute_in_term(t, from, to))
3208                    .collect();
3209                ProofTerm::Group(new_terms)
3210            }
3211            other => other.clone(),
3212        }
3213    }
3214
3215    // =========================================================================
3216    // STRATEGY 7: STRUCTURAL INDUCTION
3217    // =========================================================================
3218
3219    /// Try structural induction on inductive types (Nat, List, etc.).
3220    ///
3221    /// First attempts to infer the motive using Miller pattern unification
3222    /// (`?Motive(#n) = Goal` → `?Motive = λn.Goal`). Falls back to crude
3223    /// substitution if pattern unification fails.
3224    ///
3225    /// When the goal contains a TypedVar like `n:Nat`, we split into:
3226    /// - Base case: P(Zero)
3227    /// - Step case: ∀k. P(k) → P(Succ(k))
3228    fn try_structural_induction(
3229        &mut self,
3230        goal: &ProofGoal,
3231        depth: usize,
3232    ) -> ProofResult<Option<DerivationTree>> {
3233        // Look for TypedVar in the goal
3234        if let Some((var_name, typename)) = self.find_typed_var(&goal.target) {
3235            // Try motive inference via pattern unification first
3236            if let Some(motive) = self.try_infer_motive(&goal.target, &var_name) {
3237                match typename.as_str() {
3238                    "Nat" => {
3239                        if let Ok(Some(proof)) =
3240                            self.try_nat_induction_with_motive(goal, &var_name, &motive, depth)
3241                        {
3242                            return Ok(Some(proof));
3243                        }
3244                    }
3245                    "List" => {
3246                        // TODO: Add try_list_induction_with_motive
3247                    }
3248                    _ => {}
3249                }
3250            }
3251
3252            // Fallback: crude substitution approach
3253            match typename.as_str() {
3254                "Nat" => self.try_nat_induction(goal, &var_name, depth),
3255                "List" => self.try_list_induction(goal, &var_name, depth),
3256                _ => Ok(None), // Unknown inductive type
3257            }
3258        } else {
3259            Ok(None)
3260        }
3261    }
3262
3263    /// Try to infer the induction motive using Miller pattern unification.
3264    ///
3265    /// Given a goal like `Add(n:Nat, Zero) = n:Nat`, creates the pattern
3266    /// `?Motive(#n) = Goal` and solves for `?Motive = λn. Goal`.
3267    fn try_infer_motive(&self, goal: &ProofExpr, var_name: &str) -> Option<ProofExpr> {
3268        // Create the pattern: ?Motive(#var_name)
3269        let motive_hole = ProofExpr::Hole("Motive".to_string());
3270        let pattern = ProofExpr::App(
3271            Box::new(motive_hole),
3272            Box::new(ProofExpr::Term(ProofTerm::BoundVarRef(var_name.to_string()))),
3273        );
3274
3275        // The body is the goal itself (with TypedVar replaced by Variable for unification)
3276        let body = self.convert_typed_var_to_variable(goal, var_name);
3277
3278        // Unify: ?Motive(#n) = body
3279        match unify_pattern(&pattern, &body) {
3280            Ok(solution) => solution.get("Motive").cloned(),
3281            Err(_) => None,
3282        }
3283    }
3284
3285    /// Convert TypedVar to regular Variable for pattern unification.
3286    ///
3287    /// Pattern unification expects Variable("n") in the body to match BoundVarRef("n")
3288    /// in the pattern, but our goals have TypedVar { name: "n", typename: "Nat" }.
3289    fn convert_typed_var_to_variable(&self, expr: &ProofExpr, var_name: &str) -> ProofExpr {
3290        match expr {
3291            ProofExpr::TypedVar { name, .. } if name == var_name => {
3292                // Convert to Atom so it becomes a Variable in terms
3293                ProofExpr::Atom(name.clone())
3294            }
3295            ProofExpr::Identity(l, r) => ProofExpr::Identity(
3296                self.convert_typed_var_in_term(l, var_name),
3297                self.convert_typed_var_in_term(r, var_name),
3298            ),
3299            ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
3300                name: name.clone(),
3301                args: args
3302                    .iter()
3303                    .map(|a| self.convert_typed_var_in_term(a, var_name))
3304                    .collect(),
3305                world: world.clone(),
3306            },
3307            ProofExpr::And(l, r) => ProofExpr::And(
3308                Box::new(self.convert_typed_var_to_variable(l, var_name)),
3309                Box::new(self.convert_typed_var_to_variable(r, var_name)),
3310            ),
3311            ProofExpr::Or(l, r) => ProofExpr::Or(
3312                Box::new(self.convert_typed_var_to_variable(l, var_name)),
3313                Box::new(self.convert_typed_var_to_variable(r, var_name)),
3314            ),
3315            ProofExpr::Not(inner) => {
3316                ProofExpr::Not(Box::new(self.convert_typed_var_to_variable(inner, var_name)))
3317            }
3318            _ => expr.clone(),
3319        }
3320    }
3321
3322    /// Convert TypedVar to Variable in a ProofTerm.
3323    fn convert_typed_var_in_term(&self, term: &ProofTerm, var_name: &str) -> ProofTerm {
3324        match term {
3325            ProofTerm::Variable(v) => {
3326                // Check for "name:Type" pattern
3327                if v == var_name || v.starts_with(&format!("{}:", var_name)) {
3328                    ProofTerm::Variable(var_name.to_string())
3329                } else {
3330                    term.clone()
3331                }
3332            }
3333            ProofTerm::Function(name, args) => ProofTerm::Function(
3334                name.clone(),
3335                args.iter()
3336                    .map(|a| self.convert_typed_var_in_term(a, var_name))
3337                    .collect(),
3338            ),
3339            ProofTerm::Group(terms) => ProofTerm::Group(
3340                terms
3341                    .iter()
3342                    .map(|t| self.convert_typed_var_in_term(t, var_name))
3343                    .collect(),
3344            ),
3345            _ => term.clone(),
3346        }
3347    }
3348
3349    /// Perform structural induction on Nat using pattern unification.
3350    ///
3351    /// Uses Miller pattern unification to infer the motive, then applies
3352    /// it to constructors via beta reduction.
3353    ///
3354    /// Base case: P(Zero)
3355    /// Step case: ∀k. P(k) → P(Succ(k))
3356    fn try_nat_induction_with_motive(
3357        &mut self,
3358        goal: &ProofGoal,
3359        var_name: &str,
3360        motive: &ProofExpr,
3361        depth: usize,
3362    ) -> ProofResult<Option<DerivationTree>> {
3363        // Base case: P(Zero)
3364        // Apply the motive lambda to Zero constructor
3365        let zero_ctor = ProofExpr::Ctor {
3366            name: "Zero".into(),
3367            args: vec![],
3368        };
3369        let base_goal_expr = beta_reduce(&ProofExpr::App(
3370            Box::new(motive.clone()),
3371            Box::new(zero_ctor),
3372        ));
3373
3374        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3375        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3376            Ok(proof) => proof,
3377            Err(_) => return Ok(None),
3378        };
3379
3380        // Step case: ∀k. P(k) → P(Succ(k))
3381        let fresh_k = self.fresh_var();
3382        let k_var = ProofExpr::Atom(fresh_k.clone());
3383
3384        // Induction hypothesis: P(k)
3385        let ih = beta_reduce(&ProofExpr::App(
3386            Box::new(motive.clone()),
3387            Box::new(k_var.clone()),
3388        ));
3389
3390        // Step conclusion: P(Succ(k))
3391        let succ_k = ProofExpr::Ctor {
3392            name: "Succ".into(),
3393            args: vec![k_var],
3394        };
3395        let step_goal_expr = beta_reduce(&ProofExpr::App(
3396            Box::new(motive.clone()),
3397            Box::new(succ_k),
3398        ));
3399
3400        // Add IH to context for step case
3401        let mut step_context = goal.context.clone();
3402        step_context.push(ih.clone());
3403
3404        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3405        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3406        {
3407            Ok(proof) => proof,
3408            Err(_) => return Ok(None),
3409        };
3410
3411        Ok(Some(DerivationTree::new(
3412            goal.target.clone(),
3413            InferenceRule::StructuralInduction {
3414                variable: var_name.to_string(),
3415                ind_type: "Nat".to_string(),
3416                step_var: fresh_k,
3417            },
3418            vec![base_proof, step_proof],
3419        )))
3420    }
3421
3422    /// Perform structural induction on Nat (legacy crude substitution).
3423    ///
3424    /// Base case: P(Zero)
3425    /// Step case: ∀k. P(k) → P(Succ(k))
3426    fn try_nat_induction(
3427        &mut self,
3428        goal: &ProofGoal,
3429        var_name: &str,
3430        depth: usize,
3431    ) -> ProofResult<Option<DerivationTree>> {
3432        // Create Zero constructor
3433        let zero = ProofExpr::Ctor {
3434            name: "Zero".into(),
3435            args: vec![],
3436        };
3437
3438        // Base case: substitute Zero for the induction variable
3439        let base_goal_expr = self.substitute_typed_var(&goal.target, var_name, &zero);
3440        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3441
3442        // Try to prove base case
3443        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3444            Ok(proof) => proof,
3445            Err(_) => return Ok(None), // Can't prove base case
3446        };
3447
3448        // Step case: assume P(k), prove P(Succ(k))
3449        let fresh_k = self.fresh_var();
3450
3451        // Create k as a variable
3452        let k_var = ProofExpr::Atom(fresh_k.clone());
3453
3454        // Create Succ(k)
3455        let succ_k = ProofExpr::Ctor {
3456            name: "Succ".into(),
3457            args: vec![k_var.clone()],
3458        };
3459
3460        // Induction hypothesis: P(k)
3461        let ih = self.substitute_typed_var(&goal.target, var_name, &k_var);
3462
3463        // Step goal: P(Succ(k))
3464        let step_goal_expr = self.substitute_typed_var(&goal.target, var_name, &succ_k);
3465
3466        // Add IH to context for step case
3467        let mut step_context = goal.context.clone();
3468        step_context.push(ih.clone());
3469
3470        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3471
3472        // Try to prove step case with IH in context
3473        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3474        {
3475            Ok(proof) => proof,
3476            Err(_) => return Ok(None), // Can't prove step case
3477        };
3478
3479        // Build the induction proof tree
3480        Ok(Some(DerivationTree::new(
3481            goal.target.clone(),
3482            InferenceRule::StructuralInduction {
3483                variable: var_name.to_string(),
3484                ind_type: "Nat".to_string(),
3485                step_var: fresh_k,
3486            },
3487            vec![base_proof, step_proof],
3488        )))
3489    }
3490
3491    /// Perform structural induction on List.
3492    ///
3493    /// Base case: P(Nil)
3494    /// Step case: ∀h,t. P(t) → P(Cons(h,t))
3495    fn try_list_induction(
3496        &mut self,
3497        goal: &ProofGoal,
3498        var_name: &str,
3499        depth: usize,
3500    ) -> ProofResult<Option<DerivationTree>> {
3501        // Create Nil constructor
3502        let nil = ProofExpr::Ctor {
3503            name: "Nil".into(),
3504            args: vec![],
3505        };
3506
3507        // Base case: substitute Nil for the induction variable
3508        let base_goal_expr = self.substitute_typed_var(&goal.target, var_name, &nil);
3509        let base_goal = ProofGoal::with_context(base_goal_expr, goal.context.clone());
3510
3511        // Try to prove base case
3512        let base_proof = match self.prove_goal(base_goal, depth + 1) {
3513            Ok(proof) => proof,
3514            Err(_) => return Ok(None),
3515        };
3516
3517        // Step case: assume P(t), prove P(Cons(h, t))
3518        let fresh_h = self.fresh_var();
3519        let fresh_t = self.fresh_var();
3520
3521        let h_var = ProofExpr::Atom(fresh_h);
3522        let t_var = ProofExpr::Atom(fresh_t.clone());
3523
3524        let cons_ht = ProofExpr::Ctor {
3525            name: "Cons".into(),
3526            args: vec![h_var, t_var.clone()],
3527        };
3528
3529        // Induction hypothesis: P(t)
3530        let ih = self.substitute_typed_var(&goal.target, var_name, &t_var);
3531
3532        // Step goal: P(Cons(h, t))
3533        let step_goal_expr = self.substitute_typed_var(&goal.target, var_name, &cons_ht);
3534
3535        let mut step_context = goal.context.clone();
3536        step_context.push(ih.clone());
3537
3538        let step_goal = ProofGoal::with_context(step_goal_expr, step_context);
3539
3540        let step_proof = match self.try_step_case_with_equational_reasoning(&step_goal, &ih, depth)
3541        {
3542            Ok(proof) => proof,
3543            Err(_) => return Ok(None),
3544        };
3545
3546        Ok(Some(DerivationTree::new(
3547            goal.target.clone(),
3548            InferenceRule::StructuralInduction {
3549                variable: var_name.to_string(),
3550                ind_type: "List".to_string(),
3551                step_var: fresh_t,
3552            },
3553            vec![base_proof, step_proof],
3554        )))
3555    }
3556
3557    /// Try to prove the step case, potentially using equational reasoning.
3558    ///
3559    /// The step case often requires:
3560    /// 1. Applying a recursive axiom to simplify the goal
3561    /// 2. Using the induction hypothesis
3562    /// 3. Congruence reasoning (e.g., Succ(x) = Succ(y) if x = y)
3563    fn try_step_case_with_equational_reasoning(
3564        &mut self,
3565        goal: &ProofGoal,
3566        ih: &ProofExpr,
3567        depth: usize,
3568    ) -> ProofResult<DerivationTree> {
3569        // First, try direct proof (might work for simple cases)
3570        if let Ok(proof) = self.prove_goal(goal.clone(), depth + 1) {
3571            return Ok(proof);
3572        }
3573
3574        // For Identity goals, try equational reasoning
3575        if let ProofExpr::Identity(lhs, rhs) = &goal.target {
3576            // Try to rewrite LHS using axioms and see if we can reach RHS
3577            if let Some(proof) = self.try_equational_proof(goal, lhs, rhs, ih, depth)? {
3578                return Ok(proof);
3579            }
3580        }
3581
3582        Err(ProofError::NoProofFound)
3583    }
3584
3585    /// Try equational reasoning: rewrite LHS to match RHS using axioms and IH.
3586    ///
3587    /// For the step case of induction, we need to:
3588    /// 1. Find an axiom that matches the goal's LHS pattern
3589    /// 2. Use the axiom to rewrite LHS
3590    /// 3. Apply the induction hypothesis to simplify
3591    /// 4. Check if the result equals RHS
3592    fn try_equational_proof(
3593        &mut self,
3594        goal: &ProofGoal,
3595        lhs: &ProofTerm,
3596        rhs: &ProofTerm,
3597        ih: &ProofExpr,
3598        _depth: usize,
3599    ) -> ProofResult<Option<DerivationTree>> {
3600        // Find applicable equations from KB (Identity axioms)
3601        let equations: Vec<ProofExpr> = self
3602            .knowledge_base
3603            .iter()
3604            .filter(|e| matches!(e, ProofExpr::Identity(_, _)))
3605            .cloned()
3606            .collect();
3607
3608        // Try each equation to rewrite LHS
3609        for eq_axiom in &equations {
3610            if let ProofExpr::Identity(_, _) = &eq_axiom {
3611                // Rename variables in the axiom to avoid capture
3612                let renamed_axiom = self.rename_variables(&eq_axiom);
3613                if let ProofExpr::Identity(renamed_lhs, renamed_rhs) = renamed_axiom {
3614                    // Unify axiom LHS with goal LHS
3615                    // This binds axiom variables to goal terms
3616                    // e.g., unify(Add(Succ(x), m), Add(Succ(k), Zero)) gives {x->k, m->Zero}
3617                    if let Ok(subst) = unify_terms(&renamed_lhs, lhs) {
3618                        // Apply the substitution to the axiom's RHS
3619                        // This gives us what LHS rewrites to
3620                        let rewritten = self.apply_subst_to_term_with(&renamed_rhs, &subst);
3621
3622                        // Now check if rewritten equals RHS (possibly using IH)
3623                        if self.terms_equal_with_ih(&rewritten, rhs, ih) {
3624                            // Success! Build proof using the axiom and IH
3625                            let axiom_leaf =
3626                                DerivationTree::leaf(eq_axiom.clone(), InferenceRule::PremiseMatch);
3627
3628                            let ih_leaf =
3629                                DerivationTree::leaf(ih.clone(), InferenceRule::PremiseMatch);
3630
3631                            return Ok(Some(DerivationTree::new(
3632                                goal.target.clone(),
3633                                InferenceRule::PremiseMatch, // Equational step
3634                                vec![axiom_leaf, ih_leaf],
3635                            )));
3636                        }
3637                    }
3638                }
3639            }
3640        }
3641
3642        Ok(None)
3643    }
3644
3645    /// Check if two terms are equal, potentially using the induction hypothesis.
3646    fn terms_equal_with_ih(&self, t1: &ProofTerm, t2: &ProofTerm, ih: &ProofExpr) -> bool {
3647        // Direct equality
3648        if t1 == t2 {
3649            return true;
3650        }
3651
3652        // Try using IH: if IH is `x = y`, and t1 contains x, replace with y
3653        if let ProofExpr::Identity(ih_lhs, ih_rhs) = ih {
3654            // Check if t1 can be transformed to t2 using IH
3655            let t1_with_ih = self.rewrite_term_with_equation(t1, ih_lhs, ih_rhs);
3656            if &t1_with_ih == t2 {
3657                return true;
3658            }
3659
3660            // Also try the other direction
3661            let t2_with_ih = self.rewrite_term_with_equation(t2, ih_rhs, ih_lhs);
3662            if t1 == &t2_with_ih {
3663                return true;
3664            }
3665        }
3666
3667        false
3668    }
3669
3670    /// Rewrite occurrences of `from` to `to` in the term.
3671    fn rewrite_term_with_equation(
3672        &self,
3673        term: &ProofTerm,
3674        from: &ProofTerm,
3675        to: &ProofTerm,
3676    ) -> ProofTerm {
3677        // If term matches `from`, return `to`
3678        if term == from {
3679            return to.clone();
3680        }
3681
3682        // Recursively rewrite in subterms
3683        match term {
3684            ProofTerm::Function(name, args) => {
3685                let new_args: Vec<ProofTerm> = args
3686                    .iter()
3687                    .map(|a| self.rewrite_term_with_equation(a, from, to))
3688                    .collect();
3689                ProofTerm::Function(name.clone(), new_args)
3690            }
3691            ProofTerm::Group(terms) => {
3692                let new_terms: Vec<ProofTerm> = terms
3693                    .iter()
3694                    .map(|t| self.rewrite_term_with_equation(t, from, to))
3695                    .collect();
3696                ProofTerm::Group(new_terms)
3697            }
3698            _ => term.clone(),
3699        }
3700    }
3701
3702    /// Apply substitution to a ProofTerm with given substitution.
3703    fn apply_subst_to_term_with(&self, term: &ProofTerm, subst: &Substitution) -> ProofTerm {
3704        match term {
3705            ProofTerm::Variable(v) => subst.get(v).cloned().unwrap_or_else(|| term.clone()),
3706            ProofTerm::Function(name, args) => ProofTerm::Function(
3707                name.clone(),
3708                args.iter()
3709                    .map(|a| self.apply_subst_to_term_with(a, subst))
3710                    .collect(),
3711            ),
3712            ProofTerm::Group(terms) => ProofTerm::Group(
3713                terms
3714                    .iter()
3715                    .map(|t| self.apply_subst_to_term_with(t, subst))
3716                    .collect(),
3717            ),
3718            ProofTerm::Constant(_) => term.clone(),
3719            ProofTerm::BoundVarRef(_) => term.clone(),
3720        }
3721    }
3722
3723    /// Find a TypedVar in the expression.
3724    fn find_typed_var(&self, expr: &ProofExpr) -> Option<(String, String)> {
3725        match expr {
3726            ProofExpr::TypedVar { name, typename } => Some((name.clone(), typename.clone())),
3727            ProofExpr::Identity(l, r) => {
3728                self.find_typed_var_in_term(l).or_else(|| self.find_typed_var_in_term(r))
3729            }
3730            ProofExpr::Predicate { args, .. } => {
3731                for arg in args {
3732                    if let Some(tv) = self.find_typed_var_in_term(arg) {
3733                        return Some(tv);
3734                    }
3735                }
3736                None
3737            }
3738            ProofExpr::And(l, r)
3739            | ProofExpr::Or(l, r)
3740            | ProofExpr::Implies(l, r)
3741            | ProofExpr::Iff(l, r) => self.find_typed_var(l).or_else(|| self.find_typed_var(r)),
3742            ProofExpr::Not(inner) => self.find_typed_var(inner),
3743            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
3744                self.find_typed_var(body)
3745            }
3746            _ => None,
3747        }
3748    }
3749
3750    /// Find a TypedVar embedded in a ProofTerm.
3751    fn find_typed_var_in_term(&self, term: &ProofTerm) -> Option<(String, String)> {
3752        match term {
3753            ProofTerm::Variable(v) => {
3754                // Check if this variable name is in our KB as a TypedVar
3755                // Actually, TypedVar should be in the expression, not the term
3756                // Let's check if the variable name contains type annotation
3757                if v.contains(':') {
3758                    let parts: Vec<&str> = v.splitn(2, ':').collect();
3759                    if parts.len() == 2 {
3760                        return Some((parts[0].to_string(), parts[1].to_string()));
3761                    }
3762                }
3763                None
3764            }
3765            ProofTerm::Function(_, args) => {
3766                for arg in args {
3767                    if let Some(tv) = self.find_typed_var_in_term(arg) {
3768                        return Some(tv);
3769                    }
3770                }
3771                None
3772            }
3773            ProofTerm::Group(terms) => {
3774                for t in terms {
3775                    if let Some(tv) = self.find_typed_var_in_term(t) {
3776                        return Some(tv);
3777                    }
3778                }
3779                None
3780            }
3781            ProofTerm::Constant(_) => None,
3782            ProofTerm::BoundVarRef(_) => None, // Pattern-level, no TypedVar
3783        }
3784    }
3785
3786    /// Substitute a TypedVar with a given expression throughout the goal.
3787    fn substitute_typed_var(
3788        &self,
3789        expr: &ProofExpr,
3790        var_name: &str,
3791        replacement: &ProofExpr,
3792    ) -> ProofExpr {
3793        match expr {
3794            ProofExpr::TypedVar { name, .. } if name == var_name => replacement.clone(),
3795            ProofExpr::Identity(l, r) => {
3796                let new_l = self.substitute_typed_var_in_term(l, var_name, replacement);
3797                let new_r = self.substitute_typed_var_in_term(r, var_name, replacement);
3798                ProofExpr::Identity(new_l, new_r)
3799            }
3800            ProofExpr::Predicate { name, args, world } => {
3801                let new_args: Vec<ProofTerm> = args
3802                    .iter()
3803                    .map(|a| self.substitute_typed_var_in_term(a, var_name, replacement))
3804                    .collect();
3805                ProofExpr::Predicate {
3806                    name: name.clone(),
3807                    args: new_args,
3808                    world: world.clone(),
3809                }
3810            }
3811            ProofExpr::And(l, r) => ProofExpr::And(
3812                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3813                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3814            ),
3815            ProofExpr::Or(l, r) => ProofExpr::Or(
3816                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3817                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3818            ),
3819            ProofExpr::Implies(l, r) => ProofExpr::Implies(
3820                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3821                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3822            ),
3823            ProofExpr::Iff(l, r) => ProofExpr::Iff(
3824                Box::new(self.substitute_typed_var(l, var_name, replacement)),
3825                Box::new(self.substitute_typed_var(r, var_name, replacement)),
3826            ),
3827            ProofExpr::Not(inner) => {
3828                ProofExpr::Not(Box::new(self.substitute_typed_var(inner, var_name, replacement)))
3829            }
3830            ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
3831                variable: variable.clone(),
3832                body: Box::new(self.substitute_typed_var(body, var_name, replacement)),
3833            },
3834            ProofExpr::Exists { variable, body } => ProofExpr::Exists {
3835                variable: variable.clone(),
3836                body: Box::new(self.substitute_typed_var(body, var_name, replacement)),
3837            },
3838            _ => expr.clone(),
3839        }
3840    }
3841
3842    /// Substitute a TypedVar in a ProofTerm.
3843    fn substitute_typed_var_in_term(
3844        &self,
3845        term: &ProofTerm,
3846        var_name: &str,
3847        replacement: &ProofExpr,
3848    ) -> ProofTerm {
3849        match term {
3850            ProofTerm::Variable(v) => {
3851                // Check for TypedVar pattern "name:Type"
3852                if v == var_name || v.starts_with(&format!("{}:", var_name)) {
3853                    self.expr_to_term(replacement)
3854                } else {
3855                    term.clone()
3856                }
3857            }
3858            ProofTerm::Function(name, args) => ProofTerm::Function(
3859                name.clone(),
3860                args.iter()
3861                    .map(|a| self.substitute_typed_var_in_term(a, var_name, replacement))
3862                    .collect(),
3863            ),
3864            ProofTerm::Group(terms) => ProofTerm::Group(
3865                terms
3866                    .iter()
3867                    .map(|t| self.substitute_typed_var_in_term(t, var_name, replacement))
3868                    .collect(),
3869            ),
3870            ProofTerm::Constant(_) => term.clone(),
3871            ProofTerm::BoundVarRef(_) => term.clone(),
3872        }
3873    }
3874
3875    /// Convert a ProofExpr to a ProofTerm (for use in substitution).
3876    fn expr_to_term(&self, expr: &ProofExpr) -> ProofTerm {
3877        match expr {
3878            ProofExpr::Atom(s) => ProofTerm::Variable(s.clone()),
3879            ProofExpr::Ctor { name, args } => {
3880                ProofTerm::Function(name.clone(), args.iter().map(|a| self.expr_to_term(a)).collect())
3881            }
3882            ProofExpr::TypedVar { name, .. } => ProofTerm::Variable(name.clone()),
3883            _ => ProofTerm::Constant(format!("{}", expr)),
3884        }
3885    }
3886
3887    // =========================================================================
3888    // HELPER METHODS
3889    // =========================================================================
3890
3891    /// Generate a fresh variable name.
3892    fn fresh_var(&mut self) -> String {
3893        self.var_counter += 1;
3894        format!("_G{}", self.var_counter)
3895    }
3896
3897    /// Rename all variables in an expression to fresh names.
3898    fn rename_variables(&mut self, expr: &ProofExpr) -> ProofExpr {
3899        let vars = self.collect_variables(expr);
3900        let mut subst = Substitution::new();
3901
3902        for var in vars {
3903            let fresh = self.fresh_var();
3904            subst.insert(var, ProofTerm::Variable(fresh));
3905        }
3906
3907        apply_subst_to_expr(expr, &subst)
3908    }
3909
3910    /// Collect all variable names in an expression.
3911    fn collect_variables(&self, expr: &ProofExpr) -> Vec<String> {
3912        let mut vars = Vec::new();
3913        self.collect_variables_recursive(expr, &mut vars);
3914        vars
3915    }
3916
3917    fn collect_variables_recursive(&self, expr: &ProofExpr, vars: &mut Vec<String>) {
3918        match expr {
3919            ProofExpr::Predicate { args, .. } => {
3920                for arg in args {
3921                    self.collect_term_variables(arg, vars);
3922                }
3923            }
3924            ProofExpr::Identity(l, r) => {
3925                self.collect_term_variables(l, vars);
3926                self.collect_term_variables(r, vars);
3927            }
3928            ProofExpr::And(l, r)
3929            | ProofExpr::Or(l, r)
3930            | ProofExpr::Implies(l, r)
3931            | ProofExpr::Iff(l, r) => {
3932                self.collect_variables_recursive(l, vars);
3933                self.collect_variables_recursive(r, vars);
3934            }
3935            ProofExpr::Not(inner) => self.collect_variables_recursive(inner, vars),
3936            ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body } => {
3937                if !vars.contains(variable) {
3938                    vars.push(variable.clone());
3939                }
3940                self.collect_variables_recursive(body, vars);
3941            }
3942            ProofExpr::Lambda { variable, body } => {
3943                if !vars.contains(variable) {
3944                    vars.push(variable.clone());
3945                }
3946                self.collect_variables_recursive(body, vars);
3947            }
3948            ProofExpr::App(f, a) => {
3949                self.collect_variables_recursive(f, vars);
3950                self.collect_variables_recursive(a, vars);
3951            }
3952            ProofExpr::NeoEvent { roles, .. } => {
3953                for (_, term) in roles {
3954                    self.collect_term_variables(term, vars);
3955                }
3956            }
3957            _ => {}
3958        }
3959    }
3960
3961    fn collect_term_variables(&self, term: &ProofTerm, vars: &mut Vec<String>) {
3962        match term {
3963            ProofTerm::Variable(v) => {
3964                if !vars.contains(v) {
3965                    vars.push(v.clone());
3966                }
3967            }
3968            ProofTerm::Function(_, args) => {
3969                for arg in args {
3970                    self.collect_term_variables(arg, vars);
3971                }
3972            }
3973            ProofTerm::Group(terms) => {
3974                for t in terms {
3975                    self.collect_term_variables(t, vars);
3976                }
3977            }
3978            ProofTerm::Constant(_) => {}
3979            ProofTerm::BoundVarRef(_) => {} // Pattern-level, no variables
3980        }
3981    }
3982
3983    /// Collect potential witnesses (constants) from the knowledge base.
3984    fn collect_witnesses(&self) -> Vec<ProofTerm> {
3985        let mut witnesses = Vec::new();
3986
3987        for expr in &self.knowledge_base {
3988            self.collect_constants_from_expr(expr, &mut witnesses);
3989        }
3990
3991        witnesses
3992    }
3993
3994    fn collect_constants_from_expr(&self, expr: &ProofExpr, constants: &mut Vec<ProofTerm>) {
3995        match expr {
3996            ProofExpr::Predicate { args, .. } => {
3997                for arg in args {
3998                    self.collect_constants_from_term(arg, constants);
3999                }
4000            }
4001            ProofExpr::Identity(l, r) => {
4002                self.collect_constants_from_term(l, constants);
4003                self.collect_constants_from_term(r, constants);
4004            }
4005            ProofExpr::And(l, r)
4006            | ProofExpr::Or(l, r)
4007            | ProofExpr::Implies(l, r)
4008            | ProofExpr::Iff(l, r) => {
4009                self.collect_constants_from_expr(l, constants);
4010                self.collect_constants_from_expr(r, constants);
4011            }
4012            ProofExpr::Not(inner) => self.collect_constants_from_expr(inner, constants),
4013            ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => {
4014                self.collect_constants_from_expr(body, constants);
4015            }
4016            ProofExpr::NeoEvent { roles, .. } => {
4017                for (_, term) in roles {
4018                    self.collect_constants_from_term(term, constants);
4019                }
4020            }
4021            _ => {}
4022        }
4023    }
4024
4025    fn collect_constants_from_term(&self, term: &ProofTerm, constants: &mut Vec<ProofTerm>) {
4026        match term {
4027            ProofTerm::Constant(_) => {
4028                if !constants.contains(term) {
4029                    constants.push(term.clone());
4030                }
4031            }
4032            ProofTerm::Function(_, args) => {
4033                // The function application itself could be a witness
4034                if !constants.contains(term) {
4035                    constants.push(term.clone());
4036                }
4037                for arg in args {
4038                    self.collect_constants_from_term(arg, constants);
4039                }
4040            }
4041            ProofTerm::Group(terms) => {
4042                for t in terms {
4043                    self.collect_constants_from_term(t, constants);
4044                }
4045            }
4046            ProofTerm::Variable(_) => {}
4047            ProofTerm::BoundVarRef(_) => {} // Pattern-level, not a constant
4048        }
4049    }
4050
4051    // =========================================================================
4052    // STRATEGY 7: ORACLE FALLBACK (Z3)
4053    // =========================================================================
4054
4055    /// Attempt to prove using Z3 as an oracle.
4056    ///
4057    /// This is the fallback when all structural proof strategies fail.
4058    /// Z3 will verify arithmetic, comparisons, and uninterpreted function reasoning.
4059    #[cfg(feature = "verification")]
4060    fn try_oracle_fallback(&self, goal: &ProofGoal) -> ProofResult<Option<DerivationTree>> {
4061        crate::oracle::try_oracle(goal, &self.knowledge_base)
4062    }
4063}
4064
4065// =============================================================================
4066// HELPER FUNCTIONS
4067// =============================================================================
4068
4069/// Extract the type from an existential body if it contains type information.
4070///
4071/// Looks for TypedVar patterns in the body that might indicate the type
4072/// of the existentially quantified variable. Returns None if no type
4073/// information is found.
4074fn extract_type_from_exists_body(body: &ProofExpr) -> Option<String> {
4075    match body {
4076        // Direct TypedVar in body
4077        ProofExpr::TypedVar { typename, .. } => Some(typename.clone()),
4078
4079        // Recurse into conjunctions
4080        ProofExpr::And(l, r) => {
4081            extract_type_from_exists_body(l).or_else(|| extract_type_from_exists_body(r))
4082        }
4083
4084        // Recurse into disjunctions
4085        ProofExpr::Or(l, r) => {
4086            extract_type_from_exists_body(l).or_else(|| extract_type_from_exists_body(r))
4087        }
4088
4089        // Recurse into nested quantifiers
4090        ProofExpr::Exists { body, .. } | ProofExpr::ForAll { body, .. } => {
4091            extract_type_from_exists_body(body)
4092        }
4093
4094        // No type information found
4095        _ => None,
4096    }
4097}
4098
4099impl Default for BackwardChainer {
4100    fn default() -> Self {
4101        Self::new()
4102    }
4103}