Skip to main content

logicaffeine_kernel/
reduction.rs

1//! Term reduction for the Calculus of Constructions.
2//!
3//! This module implements the evaluation semantics of the kernel. Terms are
4//! reduced to normal form using a call-by-name strategy.
5//!
6//! # Reduction Rules
7//!
8//! ## Beta Reduction
9//! Function application: `(λx. body) arg → body[x := arg]`
10//!
11//! ## Iota Reduction
12//! Pattern matching: `match (Cᵢ args) { ... caseᵢ ... } → caseᵢ(args)`
13//!
14//! ## Fix Unfolding (Guarded)
15//! Recursive definitions: `(fix f. body) (Cᵢ args) → body[f := fix f. body] (Cᵢ args)`
16//!
17//! Fix unfolding is guarded by requiring the argument to be in constructor form.
18//! This ensures termination by structural recursion.
19//!
20//! ## Delta Reduction
21//! Global definitions are expanded when needed during normalization.
22//!
23//! # Primitive Operations
24//!
25//! The reducer handles built-in operations on literals:
26//! - Arithmetic: `add`, `sub`, `mul`, `div`, `mod`
27//! - Comparison: `lt`, `le`, `gt`, `ge`, `eq`
28//! - Boolean: `ite` (if-then-else)
29//!
30//! # Reflection Builtins
31//!
32//! Special operations for the deep embedding (Syntax type):
33//! - `syn_size`: Compute the size of a syntax tree
34//! - `syn_max_var`: Find the maximum variable index
35//! - `syn_step`, `syn_eval`: Bounded evaluation
36//! - `syn_quote`, `syn_diag`: Reification and diagonalization
37//!
38//! # Fuel Limit
39//!
40//! Normalization uses a fuel counter (default 10000) to prevent infinite loops.
41//! If fuel is exhausted, the current term is returned as-is.
42
43use crate::context::Context;
44use crate::omega;
45use crate::term::{Literal, Term, Universe};
46use crate::type_checker::substitute;
47
48/// Normalize a term to its normal form.
49///
50/// Repeatedly applies reduction rules until no more reductions are possible.
51/// This is a full normalization that reduces under binders.
52pub fn normalize(ctx: &Context, term: &Term) -> Term {
53    let mut current = term.clone();
54    let mut fuel = 10000; // Safety limit to prevent infinite loops
55
56    loop {
57        if fuel == 0 {
58            // If we hit the fuel limit, return what we have
59            return current;
60        }
61        fuel -= 1;
62
63        let reduced = reduce_step(ctx, &current);
64        if reduced == current {
65            return current;
66        }
67        current = reduced;
68    }
69}
70
71/// Single-step reduction.
72///
73/// Reduces the outermost redex first (call-by-name), then recurses
74/// into subterms for full normalization.
75fn reduce_step(ctx: &Context, term: &Term) -> Term {
76    match term {
77        // Literals are in normal form
78        Term::Lit(_) => term.clone(),
79
80        // Beta: (λx. body) arg → body[x := arg]
81        Term::App(func, arg) => {
82            // First try primitive reduction (add, sub, mul, etc.)
83            if let Some(result) = try_primitive_reduce(func, arg) {
84                return result;
85            }
86
87            // Try reflection builtins (syn_size, syn_max_var)
88            if let Some(result) = try_reflection_reduce(ctx, func, arg) {
89                return result;
90            }
91
92            // Then try to reduce at the head
93            match func.as_ref() {
94                Term::Lambda { param, body, .. } => {
95                    // Beta reduction
96                    substitute(body, param, arg)
97                }
98                // Fix application: (fix f. body) arg
99                // We need to check if arg is a constructor to unfold
100                Term::Fix { name, body } => {
101                    if is_constructor_form(ctx, arg) {
102                        // Unfold: (fix f. body) arg → body[f := fix f. body] arg
103                        let fix_term = Term::Fix {
104                            name: name.clone(),
105                            body: body.clone(),
106                        };
107                        let unfolded = substitute(body, name, &fix_term);
108                        Term::App(Box::new(unfolded), arg.clone())
109                    } else {
110                        // Try reducing the argument first
111                        let reduced_arg = reduce_step(ctx, arg);
112                        if reduced_arg != **arg {
113                            Term::App(func.clone(), Box::new(reduced_arg))
114                        } else {
115                            term.clone()
116                        }
117                    }
118                }
119                // Nested application: ((f x) y) - reduce inner first
120                Term::App(_, _) => {
121                    let reduced_func = reduce_step(ctx, func);
122                    if reduced_func != **func {
123                        Term::App(Box::new(reduced_func), arg.clone())
124                    } else {
125                        // Try reducing argument
126                        let reduced_arg = reduce_step(ctx, arg);
127                        Term::App(func.clone(), Box::new(reduced_arg))
128                    }
129                }
130                // Other function forms - try reducing function position
131                _ => {
132                    let reduced_func = reduce_step(ctx, func);
133                    if reduced_func != **func {
134                        Term::App(Box::new(reduced_func), arg.clone())
135                    } else {
136                        // Try reducing argument
137                        let reduced_arg = reduce_step(ctx, arg);
138                        Term::App(func.clone(), Box::new(reduced_arg))
139                    }
140                }
141            }
142        }
143
144        // Iota: match (Cᵢ a₁...aₙ) with cases → caseᵢ a₁ ... aₙ
145        Term::Match {
146            discriminant,
147            motive,
148            cases,
149        } => {
150            if let Some((ctor_idx, args)) = extract_constructor(ctx, discriminant) {
151                // Select the corresponding case
152                let case = &cases[ctor_idx];
153                // Apply case to constructor arguments
154                let mut result = case.clone();
155                for arg in args {
156                    result = Term::App(Box::new(result), Box::new(arg));
157                }
158                // Reduce the result
159                reduce_step(ctx, &result)
160            } else {
161                // Try reducing the discriminant
162                let reduced_disc = reduce_step(ctx, discriminant);
163                if reduced_disc != **discriminant {
164                    Term::Match {
165                        discriminant: Box::new(reduced_disc),
166                        motive: motive.clone(),
167                        cases: cases.clone(),
168                    }
169                } else {
170                    term.clone()
171                }
172            }
173        }
174
175        // Reduce under lambdas (deep normalization)
176        Term::Lambda {
177            param,
178            param_type,
179            body,
180        } => {
181            let reduced_param_type = reduce_step(ctx, param_type);
182            let reduced_body = reduce_step(ctx, body);
183            if reduced_param_type != **param_type || reduced_body != **body {
184                Term::Lambda {
185                    param: param.clone(),
186                    param_type: Box::new(reduced_param_type),
187                    body: Box::new(reduced_body),
188                }
189            } else {
190                term.clone()
191            }
192        }
193
194        // Reduce under Pi types
195        Term::Pi {
196            param,
197            param_type,
198            body_type,
199        } => {
200            let reduced_param_type = reduce_step(ctx, param_type);
201            let reduced_body_type = reduce_step(ctx, body_type);
202            if reduced_param_type != **param_type || reduced_body_type != **body_type {
203                Term::Pi {
204                    param: param.clone(),
205                    param_type: Box::new(reduced_param_type),
206                    body_type: Box::new(reduced_body_type),
207                }
208            } else {
209                term.clone()
210            }
211        }
212
213        // Reduce under Fix
214        Term::Fix { name, body } => {
215            let reduced_body = reduce_step(ctx, body);
216            if reduced_body != **body {
217                Term::Fix {
218                    name: name.clone(),
219                    body: Box::new(reduced_body),
220                }
221            } else {
222                term.clone()
223            }
224        }
225
226        // Base cases: already in normal form
227        Term::Sort(_) | Term::Var(_) | Term::Hole => term.clone(),
228
229        // Delta reduction: unfold definitions (but not axioms, constructors, or inductives)
230        Term::Global(name) => {
231            if let Some(body) = ctx.get_definition_body(name) {
232                // Definition found - unfold to body
233                body.clone()
234            } else {
235                // Axiom, constructor, or inductive - no reduction
236                term.clone()
237            }
238        }
239    }
240}
241
242/// Check if a term is a constructor (possibly applied to arguments).
243fn is_constructor_form(ctx: &Context, term: &Term) -> bool {
244    extract_constructor(ctx, term).is_some()
245}
246
247/// Extract constructor index and VALUE arguments from a term (skipping type arguments).
248///
249/// Returns `Some((constructor_index, [value_arg1, value_arg2, ...]))` if term is `Cᵢ(type_args..., value_args...)`
250/// where `Cᵢ` is the i-th constructor of some inductive type.
251///
252/// For polymorphic constructors like `Cons A h t`, this returns only [h, t], not [A, h, t].
253fn extract_constructor(ctx: &Context, term: &Term) -> Option<(usize, Vec<Term>)> {
254    let mut args = Vec::new();
255    let mut current = term;
256
257    // Collect arguments from nested applications
258    while let Term::App(func, arg) = current {
259        args.push((**arg).clone());
260        current = func;
261    }
262    args.reverse();
263
264    // Check if head is a constructor
265    if let Term::Global(name) = current {
266        if let Some(inductive) = ctx.constructor_inductive(name) {
267            let ctors = ctx.get_constructors(inductive);
268            for (idx, (ctor_name, ctor_type)) in ctors.iter().enumerate() {
269                if *ctor_name == name {
270                    // Count type parameters (leading Pis where param_type is a Sort)
271                    let num_type_params = count_type_params(ctor_type);
272
273                    // Skip type arguments, return only value arguments
274                    let value_args = if num_type_params < args.len() {
275                        args[num_type_params..].to_vec()
276                    } else {
277                        vec![]
278                    };
279
280                    return Some((idx, value_args));
281                }
282            }
283        }
284    }
285    None
286}
287
288/// Count leading type parameters in a constructor type.
289///
290/// Type parameters are Pis where the param_type is a Sort (Type n or Prop).
291/// For `Π(A:Type). Π(_:A). Π(_:List A). List A`, this returns 1.
292fn count_type_params(ty: &Term) -> usize {
293    let mut count = 0;
294    let mut current = ty;
295
296    while let Term::Pi { param_type, body_type, .. } = current {
297        if is_sort(param_type) {
298            count += 1;
299            current = body_type;
300        } else {
301            break;
302        }
303    }
304
305    count
306}
307
308/// Check if a term is a Sort (Type n or Prop).
309fn is_sort(term: &Term) -> bool {
310    matches!(term, Term::Sort(_))
311}
312
313/// Try to reduce a primitive operation.
314///
315/// Returns Some(result) if this is a fully applied primitive like (add 3 4).
316/// Pattern: ((op x) y) where op is a builtin and x, y are literals.
317fn try_primitive_reduce(func: &Term, arg: &Term) -> Option<Term> {
318    // We need func = (op x) and arg = y
319    if let Term::App(op_term, x) = func {
320        if let Term::Global(op_name) = op_term.as_ref() {
321            if let (Term::Lit(Literal::Int(x_val)), Term::Lit(Literal::Int(y_val))) =
322                (x.as_ref(), arg)
323            {
324                let result = match op_name.as_str() {
325                    "add" => x_val.checked_add(*y_val)?,
326                    "sub" => x_val.checked_sub(*y_val)?,
327                    "mul" => x_val.checked_mul(*y_val)?,
328                    "div" => x_val.checked_div(*y_val)?,
329                    "mod" => x_val.checked_rem(*y_val)?,
330                    _ => return None,
331                };
332                return Some(Term::Lit(Literal::Int(result)));
333            }
334        }
335    }
336    None
337}
338
339// -------------------------------------------------------------------------
340// Reflection Builtins
341// -------------------------------------------------------------------------
342
343/// Try to reduce reflection builtins (syn_size, syn_max_var, syn_lift, syn_subst, syn_beta, syn_step).
344///
345/// Pattern: (syn_size arg) or (syn_max_var arg) or (syn_step arg) where arg is a Syntax constructor.
346/// Pattern: ((syn_beta body) arg) for two-argument builtins.
347/// Pattern: (((syn_lift amount) cutoff) term) for three-argument builtins.
348/// Pattern: (((syn_subst replacement) index) term) for substitution.
349fn try_reflection_reduce(ctx: &Context, func: &Term, arg: &Term) -> Option<Term> {
350    // First, check for single-argument builtins
351    if let Term::Global(op_name) = func {
352        match op_name.as_str() {
353            "syn_size" => {
354                // Normalize the argument first
355                let norm_arg = normalize(ctx, arg);
356                return try_syn_size_reduce(ctx, &norm_arg);
357            }
358            "syn_max_var" => {
359                // Normalize the argument first
360                let norm_arg = normalize(ctx, arg);
361                return try_syn_max_var_reduce(ctx, &norm_arg, 0);
362            }
363            "syn_step" => {
364                // Normalize the argument first
365                let norm_arg = normalize(ctx, arg);
366                return try_syn_step_reduce(ctx, &norm_arg);
367            }
368            "syn_quote" => {
369                // Normalize the argument first
370                let norm_arg = normalize(ctx, arg);
371                return try_syn_quote_reduce(ctx, &norm_arg);
372            }
373            "syn_diag" => {
374                // Normalize the argument first
375                let norm_arg = normalize(ctx, arg);
376                return try_syn_diag_reduce(ctx, &norm_arg);
377            }
378            "concludes" => {
379                // Normalize the argument first
380                let norm_arg = normalize(ctx, arg);
381                return try_concludes_reduce(ctx, &norm_arg);
382            }
383            "try_refl" => {
384                // Normalize the argument first
385                let norm_arg = normalize(ctx, arg);
386                return try_try_refl_reduce(ctx, &norm_arg);
387            }
388            "tact_fail" => {
389                // tact_fail always returns error derivation
390                return Some(make_error_derivation());
391            }
392            "try_compute" => {
393                // try_compute goal = DCompute goal
394                // The validation happens in concludes
395                let norm_arg = normalize(ctx, arg);
396                return Some(Term::App(
397                    Box::new(Term::Global("DCompute".to_string())),
398                    Box::new(norm_arg),
399                ));
400            }
401            "try_ring" => {
402                // Ring tactic: prove polynomial equalities by normalization
403                let norm_arg = normalize(ctx, arg);
404                return try_try_ring_reduce(ctx, &norm_arg);
405            }
406            "try_lia" => {
407                // LIA tactic: prove linear inequalities by Fourier-Motzkin
408                let norm_arg = normalize(ctx, arg);
409                return try_try_lia_reduce(ctx, &norm_arg);
410            }
411            "try_cc" => {
412                // CC tactic: prove equalities by congruence closure
413                let norm_arg = normalize(ctx, arg);
414                return try_try_cc_reduce(ctx, &norm_arg);
415            }
416            "try_simp" => {
417                // Simp tactic: prove equalities by simplification and arithmetic
418                let norm_arg = normalize(ctx, arg);
419                return try_try_simp_reduce(ctx, &norm_arg);
420            }
421            "try_omega" => {
422                // Omega tactic: prove integer inequalities with proper rounding
423                let norm_arg = normalize(ctx, arg);
424                return try_try_omega_reduce(ctx, &norm_arg);
425            }
426            "try_auto" => {
427                // Auto tactic: tries all tactics in sequence
428                let norm_arg = normalize(ctx, arg);
429                return try_try_auto_reduce(ctx, &norm_arg);
430            }
431            "try_inversion" => {
432                // Inversion tactic: derives False if no constructor can match
433                let norm_arg = normalize(ctx, arg);
434                return try_try_inversion_reduce(ctx, &norm_arg);
435            }
436            "induction_num_cases" => {
437                // Returns number of constructors for an inductive type
438                let norm_arg = normalize(ctx, arg);
439                return try_induction_num_cases_reduce(ctx, &norm_arg);
440            }
441            _ => {}
442        }
443    }
444
445    // For multi-argument builtins like syn_lift and syn_subst,
446    // we need to check if func is a partial application
447
448    // syn_lift amount cutoff term
449    // Structure: (((syn_lift amount) cutoff) term) = App(App(App(syn_lift, amount), cutoff), term)
450    // When reduced: func = App(App(syn_lift, amount), cutoff), arg = term
451    if let Term::App(partial2, cutoff) = func {
452        if let Term::App(partial1, amount) = partial2.as_ref() {
453            if let Term::Global(op_name) = partial1.as_ref() {
454                if op_name == "syn_lift" {
455                    if let (Term::Lit(Literal::Int(amt)), Term::Lit(Literal::Int(cut))) =
456                        (amount.as_ref(), cutoff.as_ref())
457                    {
458                        let norm_term = normalize(ctx, arg);
459                        return try_syn_lift_reduce(ctx, *amt, *cut, &norm_term);
460                    }
461                }
462            }
463        }
464    }
465
466    // syn_subst replacement index term
467    // Structure: (((syn_subst replacement) index) term)
468    if let Term::App(partial2, index) = func {
469        if let Term::App(partial1, replacement) = partial2.as_ref() {
470            if let Term::Global(op_name) = partial1.as_ref() {
471                if op_name == "syn_subst" {
472                    if let Term::Lit(Literal::Int(idx)) = index.as_ref() {
473                        let norm_replacement = normalize(ctx, replacement);
474                        let norm_term = normalize(ctx, arg);
475                        return try_syn_subst_reduce(ctx, &norm_replacement, *idx, &norm_term);
476                    }
477                }
478            }
479        }
480    }
481
482    // syn_beta body arg (2 arguments)
483    // Structure: ((syn_beta body) arg)
484    if let Term::App(partial1, body) = func {
485        if let Term::Global(op_name) = partial1.as_ref() {
486            if op_name == "syn_beta" {
487                let norm_body = normalize(ctx, body);
488                let norm_arg = normalize(ctx, arg);
489                return try_syn_beta_reduce(ctx, &norm_body, &norm_arg);
490            }
491        }
492    }
493
494    // try_cong context eq_proof (2 arguments)
495    // Structure: ((try_cong context) eq_proof)
496    // Returns: DCong context eq_proof
497    if let Term::App(partial1, context) = func {
498        if let Term::Global(op_name) = partial1.as_ref() {
499            if op_name == "try_cong" {
500                let norm_context = normalize(ctx, context);
501                let norm_proof = normalize(ctx, arg);
502                return Some(Term::App(
503                    Box::new(Term::App(
504                        Box::new(Term::Global("DCong".to_string())),
505                        Box::new(norm_context),
506                    )),
507                    Box::new(norm_proof),
508                ));
509            }
510        }
511    }
512
513    // try_rewrite eq_proof goal (2 arguments)
514    // Structure: ((try_rewrite eq_proof) goal)
515    // Given eq_proof : Eq A x y, rewrites goal by replacing x with y
516    if let Term::App(partial1, eq_proof) = func {
517        if let Term::Global(op_name) = partial1.as_ref() {
518            if op_name == "try_rewrite" {
519                let norm_eq_proof = normalize(ctx, eq_proof);
520                let norm_goal = normalize(ctx, arg);
521                return try_try_rewrite_reduce(ctx, &norm_eq_proof, &norm_goal, false);
522            }
523            if op_name == "try_rewrite_rev" {
524                let norm_eq_proof = normalize(ctx, eq_proof);
525                let norm_goal = normalize(ctx, arg);
526                return try_try_rewrite_reduce(ctx, &norm_eq_proof, &norm_goal, true);
527            }
528        }
529    }
530
531    // syn_eval fuel term (2 arguments)
532    // Structure: ((syn_eval fuel) term)
533    if let Term::App(partial1, fuel_term) = func {
534        if let Term::Global(op_name) = partial1.as_ref() {
535            if op_name == "syn_eval" {
536                if let Term::Lit(Literal::Int(fuel)) = fuel_term.as_ref() {
537                    let norm_term = normalize(ctx, arg);
538                    return try_syn_eval_reduce(ctx, *fuel, &norm_term);
539                }
540            }
541        }
542    }
543
544    // induction_base_goal ind_type motive (2 arguments)
545    // Structure: ((induction_base_goal ind_type) motive)
546    if let Term::App(partial1, ind_type) = func {
547        if let Term::Global(op_name) = partial1.as_ref() {
548            if op_name == "induction_base_goal" {
549                let norm_ind_type = normalize(ctx, ind_type);
550                let norm_motive = normalize(ctx, arg);
551                return try_induction_base_goal_reduce(ctx, &norm_ind_type, &norm_motive);
552            }
553        }
554    }
555
556    // tact_orelse t1 t2 goal (3 arguments)
557    // Structure: (((tact_orelse t1) t2) goal)
558    // Here: func = App(App(tact_orelse, t1), t2), arg = goal
559    if let Term::App(partial1, t2) = func {
560        if let Term::App(combinator, t1) = partial1.as_ref() {
561            if let Term::Global(name) = combinator.as_ref() {
562                if name == "tact_orelse" {
563                    return try_tact_orelse_reduce(ctx, t1, t2, arg);
564                }
565                // tact_then t1 t2 goal (3 arguments)
566                if name == "tact_then" {
567                    return try_tact_then_reduce(ctx, t1, t2, arg);
568                }
569            }
570        }
571    }
572
573    // tact_try t goal (2 arguments)
574    // Structure: ((tact_try t) goal)
575    if let Term::App(combinator, t) = func {
576        if let Term::Global(name) = combinator.as_ref() {
577            if name == "tact_try" {
578                return try_tact_try_reduce(ctx, t, arg);
579            }
580            // tact_repeat t goal (2 arguments)
581            if name == "tact_repeat" {
582                return try_tact_repeat_reduce(ctx, t, arg);
583            }
584            // tact_solve t goal (2 arguments)
585            if name == "tact_solve" {
586                return try_tact_solve_reduce(ctx, t, arg);
587            }
588            // tact_first tactics goal (2 arguments)
589            if name == "tact_first" {
590                return try_tact_first_reduce(ctx, t, arg);
591            }
592        }
593    }
594
595    // induction_step_goal ind_type motive ctor_idx (3 arguments)
596    // Structure: (((induction_step_goal ind_type) motive) ctor_idx)
597    // Returns the goal for the given constructor index
598    if let Term::App(partial1, motive) = func {
599        if let Term::App(combinator, ind_type) = partial1.as_ref() {
600            if let Term::Global(name) = combinator.as_ref() {
601                if name == "induction_step_goal" {
602                    let norm_ind_type = normalize(ctx, ind_type);
603                    let norm_motive = normalize(ctx, motive);
604                    let norm_idx = normalize(ctx, arg);
605                    return try_induction_step_goal_reduce(ctx, &norm_ind_type, &norm_motive, &norm_idx);
606                }
607            }
608        }
609    }
610
611    // try_induction ind_type motive cases (3 arguments)
612    // Structure: (((try_induction ind_type) motive) cases)
613    // Returns DElim ind_type motive cases (delegating to existing infrastructure)
614    if let Term::App(partial1, motive) = func {
615        if let Term::App(combinator, ind_type) = partial1.as_ref() {
616            if let Term::Global(name) = combinator.as_ref() {
617                if name == "try_induction" {
618                    let norm_ind_type = normalize(ctx, ind_type);
619                    let norm_motive = normalize(ctx, motive);
620                    let norm_cases = normalize(ctx, arg);
621                    return try_try_induction_reduce(ctx, &norm_ind_type, &norm_motive, &norm_cases);
622                }
623            }
624        }
625    }
626
627    // try_destruct ind_type motive cases (3 arguments)
628    // Structure: (((try_destruct ind_type) motive) cases)
629    // Case analysis without induction hypotheses
630    if let Term::App(partial1, motive) = func {
631        if let Term::App(combinator, ind_type) = partial1.as_ref() {
632            if let Term::Global(name) = combinator.as_ref() {
633                if name == "try_destruct" {
634                    let norm_ind_type = normalize(ctx, ind_type);
635                    let norm_motive = normalize(ctx, motive);
636                    let norm_cases = normalize(ctx, arg);
637                    return try_try_destruct_reduce(ctx, &norm_ind_type, &norm_motive, &norm_cases);
638                }
639            }
640        }
641    }
642
643    // try_apply hyp_name hyp_proof goal (3 arguments)
644    // Structure: (((try_apply hyp_name) hyp_proof) goal)
645    // Manual backward chaining
646    if let Term::App(partial1, hyp_proof) = func {
647        if let Term::App(combinator, hyp_name) = partial1.as_ref() {
648            if let Term::Global(name) = combinator.as_ref() {
649                if name == "try_apply" {
650                    let norm_hyp_name = normalize(ctx, hyp_name);
651                    let norm_hyp_proof = normalize(ctx, hyp_proof);
652                    let norm_goal = normalize(ctx, arg);
653                    return try_try_apply_reduce(ctx, &norm_hyp_name, &norm_hyp_proof, &norm_goal);
654                }
655            }
656        }
657    }
658
659    None
660}
661
662/// Compute size of a Syntax term.
663///
664/// SVar n -> 1
665/// SGlobal n -> 1
666/// SSort u -> 1
667/// SApp f x -> 1 + size(f) + size(x)
668/// SLam A b -> 1 + size(A) + size(b)
669/// SPi A B -> 1 + size(A) + size(B)
670fn try_syn_size_reduce(ctx: &Context, term: &Term) -> Option<Term> {
671    // Match on the constructor form
672    // Unary constructors: (SVar n), (SGlobal n), (SSort u)
673    // Binary constructors: ((SApp f) x), ((SLam A) b), ((SPi A) B)
674
675    // Check for unary constructor: (Ctor arg)
676    if let Term::App(ctor_term, _inner_arg) = term {
677        if let Term::Global(ctor_name) = ctor_term.as_ref() {
678            match ctor_name.as_str() {
679                "SVar" | "SGlobal" | "SSort" | "SLit" | "SName" => {
680                    return Some(Term::Lit(Literal::Int(1)));
681                }
682                _ => {}
683            }
684        }
685
686        // Check for binary constructor: ((Ctor a) b)
687        if let Term::App(inner, a) = ctor_term.as_ref() {
688            if let Term::Global(ctor_name) = inner.as_ref() {
689                match ctor_name.as_str() {
690                    "SApp" | "SLam" | "SPi" => {
691                        // Get sizes of both children
692                        let a_size = try_syn_size_reduce(ctx, a)?;
693                        let b_size = try_syn_size_reduce(ctx, _inner_arg)?;
694
695                        if let (Term::Lit(Literal::Int(a_n)), Term::Lit(Literal::Int(b_n))) =
696                            (a_size, b_size)
697                        {
698                            return Some(Term::Lit(Literal::Int(1 + a_n + b_n)));
699                        }
700                    }
701                    _ => {}
702                }
703            }
704        }
705    }
706
707    None
708}
709
710/// Compute maximum free variable index in a Syntax term.
711///
712/// The `depth` parameter tracks how many binders we're under.
713/// SVar k -> k - depth (returns -1 if bound, k - depth if free)
714/// SGlobal _ -> -1 (no free variables)
715/// SSort _ -> -1 (no free variables)
716/// SApp f x -> max(max_var(f), max_var(x))
717/// SLam A b -> max(max_var(A), max_var(b, depth+1))
718/// SPi A B -> max(max_var(A), max_var(B, depth+1))
719fn try_syn_max_var_reduce(ctx: &Context, term: &Term, depth: i64) -> Option<Term> {
720    // Check for unary constructor: (Ctor arg)
721    if let Term::App(ctor_term, inner_arg) = term {
722        if let Term::Global(ctor_name) = ctor_term.as_ref() {
723            match ctor_name.as_str() {
724                "SVar" => {
725                    // SVar k -> if k >= depth then k - depth else -1
726                    if let Term::Lit(Literal::Int(k)) = inner_arg.as_ref() {
727                        if *k >= depth {
728                            // Free variable with adjusted index
729                            return Some(Term::Lit(Literal::Int(*k - depth)));
730                        } else {
731                            // Bound variable
732                            return Some(Term::Lit(Literal::Int(-1)));
733                        }
734                    }
735                }
736                "SGlobal" | "SSort" | "SLit" | "SName" => {
737                    // No free variables
738                    return Some(Term::Lit(Literal::Int(-1)));
739                }
740                _ => {}
741            }
742        }
743
744        // Check for binary constructor: ((Ctor a) b)
745        if let Term::App(inner, a) = ctor_term.as_ref() {
746            if let Term::Global(ctor_name) = inner.as_ref() {
747                match ctor_name.as_str() {
748                    "SApp" => {
749                        // SApp f x -> max(max_var(f), max_var(x))
750                        let a_max = try_syn_max_var_reduce(ctx, a, depth)?;
751                        let b_max = try_syn_max_var_reduce(ctx, inner_arg, depth)?;
752
753                        if let (Term::Lit(Literal::Int(a_n)), Term::Lit(Literal::Int(b_n))) =
754                            (a_max, b_max)
755                        {
756                            return Some(Term::Lit(Literal::Int(a_n.max(b_n))));
757                        }
758                    }
759                    "SLam" | "SPi" => {
760                        // SLam A b -> max(max_var(A, depth), max_var(b, depth+1))
761                        // The body 'b' is under one additional binder
762                        let a_max = try_syn_max_var_reduce(ctx, a, depth)?;
763                        let b_max = try_syn_max_var_reduce(ctx, inner_arg, depth + 1)?;
764
765                        if let (Term::Lit(Literal::Int(a_n)), Term::Lit(Literal::Int(b_n))) =
766                            (a_max, b_max)
767                        {
768                            return Some(Term::Lit(Literal::Int(a_n.max(b_n))));
769                        }
770                    }
771                    _ => {}
772                }
773            }
774        }
775    }
776
777    None
778}
779
780// -------------------------------------------------------------------------
781// Substitution Builtins
782// -------------------------------------------------------------------------
783
784/// Lift De Bruijn indices in a Syntax term.
785///
786/// syn_lift amount cutoff term:
787/// - Variables with index < cutoff are bound -> unchanged
788/// - Variables with index >= cutoff are free -> add amount
789fn try_syn_lift_reduce(ctx: &Context, amount: i64, cutoff: i64, term: &Term) -> Option<Term> {
790    // Check for unary constructor: (Ctor arg)
791    if let Term::App(ctor_term, inner_arg) = term {
792        if let Term::Global(ctor_name) = ctor_term.as_ref() {
793            match ctor_name.as_str() {
794                "SVar" => {
795                    if let Term::Lit(Literal::Int(k)) = inner_arg.as_ref() {
796                        if *k >= cutoff {
797                            // Free variable, shift
798                            return Some(Term::App(
799                                Box::new(Term::Global("SVar".to_string())),
800                                Box::new(Term::Lit(Literal::Int(*k + amount))),
801                            ));
802                        } else {
803                            // Bound variable, no shift
804                            return Some(term.clone());
805                        }
806                    }
807                }
808                "SGlobal" | "SSort" | "SLit" | "SName" => {
809                    // No free variables
810                    return Some(term.clone());
811                }
812                _ => {}
813            }
814        }
815
816        // Check for binary constructor: ((Ctor a) b)
817        if let Term::App(inner, a) = ctor_term.as_ref() {
818            if let Term::Global(ctor_name) = inner.as_ref() {
819                match ctor_name.as_str() {
820                    "SApp" => {
821                        // No binding, same cutoff for both
822                        let a_lifted = try_syn_lift_reduce(ctx, amount, cutoff, a)?;
823                        let b_lifted = try_syn_lift_reduce(ctx, amount, cutoff, inner_arg)?;
824                        return Some(Term::App(
825                            Box::new(Term::App(
826                                Box::new(Term::Global("SApp".to_string())),
827                                Box::new(a_lifted),
828                            )),
829                            Box::new(b_lifted),
830                        ));
831                    }
832                    "SLam" | "SPi" => {
833                        // Binder: param type at current cutoff, body at cutoff+1
834                        let param_lifted = try_syn_lift_reduce(ctx, amount, cutoff, a)?;
835                        let body_lifted =
836                            try_syn_lift_reduce(ctx, amount, cutoff + 1, inner_arg)?;
837                        return Some(Term::App(
838                            Box::new(Term::App(
839                                Box::new(Term::Global(ctor_name.clone())),
840                                Box::new(param_lifted),
841                            )),
842                            Box::new(body_lifted),
843                        ));
844                    }
845                    _ => {}
846                }
847            }
848        }
849    }
850
851    None
852}
853
854/// Substitute a term for a variable in a Syntax term.
855///
856/// syn_subst replacement index term:
857/// - If term is SVar k and k == index, return replacement
858/// - If term is SVar k and k != index, return term unchanged
859/// - For binders, increment index and lift replacement
860fn try_syn_subst_reduce(
861    ctx: &Context,
862    replacement: &Term,
863    index: i64,
864    term: &Term,
865) -> Option<Term> {
866    // Check for unary constructor: (Ctor arg)
867    if let Term::App(ctor_term, inner_arg) = term {
868        if let Term::Global(ctor_name) = ctor_term.as_ref() {
869            match ctor_name.as_str() {
870                "SVar" => {
871                    if let Term::Lit(Literal::Int(k)) = inner_arg.as_ref() {
872                        if *k == index {
873                            // Match! Return replacement
874                            return Some(replacement.clone());
875                        } else {
876                            // No match, return unchanged
877                            return Some(term.clone());
878                        }
879                    }
880                }
881                "SGlobal" | "SSort" | "SLit" | "SName" => {
882                    // No variables to substitute
883                    return Some(term.clone());
884                }
885                _ => {}
886            }
887        }
888
889        // Check for binary constructor: ((Ctor a) b)
890        if let Term::App(inner, a) = ctor_term.as_ref() {
891            if let Term::Global(ctor_name) = inner.as_ref() {
892                match ctor_name.as_str() {
893                    "SApp" => {
894                        // No binding, same index and replacement
895                        let a_subst = try_syn_subst_reduce(ctx, replacement, index, a)?;
896                        let b_subst = try_syn_subst_reduce(ctx, replacement, index, inner_arg)?;
897                        return Some(Term::App(
898                            Box::new(Term::App(
899                                Box::new(Term::Global("SApp".to_string())),
900                                Box::new(a_subst),
901                            )),
902                            Box::new(b_subst),
903                        ));
904                    }
905                    "SLam" | "SPi" => {
906                        // Binder: param type at current index, body at index+1
907                        // Lift replacement when going under binder
908                        let param_subst = try_syn_subst_reduce(ctx, replacement, index, a)?;
909
910                        // Lift the replacement by 1 when going under the binder
911                        let lifted_replacement = try_syn_lift_reduce(ctx, 1, 0, replacement)?;
912                        let body_subst = try_syn_subst_reduce(
913                            ctx,
914                            &lifted_replacement,
915                            index + 1,
916                            inner_arg,
917                        )?;
918
919                        return Some(Term::App(
920                            Box::new(Term::App(
921                                Box::new(Term::Global(ctor_name.clone())),
922                                Box::new(param_subst),
923                            )),
924                            Box::new(body_subst),
925                        ));
926                    }
927                    _ => {}
928                }
929            }
930        }
931    }
932
933    None
934}
935
936// -------------------------------------------------------------------------
937// Computation Builtins
938// -------------------------------------------------------------------------
939
940/// Beta reduction: substitute arg for variable 0 in body.
941///
942/// syn_beta body arg = syn_subst arg 0 body
943fn try_syn_beta_reduce(ctx: &Context, body: &Term, arg: &Term) -> Option<Term> {
944    // syn_beta is just syn_subst with index 0
945    try_syn_subst_reduce(ctx, arg, 0, body)
946}
947
948/// Try to reduce arithmetic on Syntax literals.
949///
950/// Handles: SApp (SApp (SName op) (SLit n)) (SLit m) → SLit result
951/// where op is one of: add, sub, mul, div, mod
952fn try_syn_arith_reduce(func: &Term, arg: &Term) -> Option<Term> {
953    // func should be: SApp (SName op) (SLit n)
954    // Pattern: App(App(Global("SApp"), App(Global("SName"), Lit(Text(op)))), App(Global("SLit"), Lit(Int(n))))
955    if let Term::App(inner_ctor, n_term) = func {
956        if let Term::App(sapp_ctor, op_term) = inner_ctor.as_ref() {
957            // Check for SApp constructor
958            if let Term::Global(ctor_name) = sapp_ctor.as_ref() {
959                if ctor_name != "SApp" {
960                    return None;
961                }
962            } else {
963                return None;
964            }
965
966            // Extract op from SName op
967            let op = if let Term::App(sname_ctor, op_str) = op_term.as_ref() {
968                if let Term::Global(name) = sname_ctor.as_ref() {
969                    if name == "SName" {
970                        if let Term::Lit(Literal::Text(op)) = op_str.as_ref() {
971                            op.clone()
972                        } else {
973                            return None;
974                        }
975                    } else {
976                        return None;
977                    }
978                } else {
979                    return None;
980                }
981            } else {
982                return None;
983            };
984
985            // Extract n from SLit n
986            let n = if let Term::App(slit_ctor, n_val) = n_term.as_ref() {
987                if let Term::Global(name) = slit_ctor.as_ref() {
988                    if name == "SLit" {
989                        if let Term::Lit(Literal::Int(n)) = n_val.as_ref() {
990                            *n
991                        } else {
992                            return None;
993                        }
994                    } else {
995                        return None;
996                    }
997                } else {
998                    return None;
999                }
1000            } else {
1001                return None;
1002            };
1003
1004            // Extract m from SLit m (the arg)
1005            let m = if let Term::App(slit_ctor, m_val) = arg {
1006                if let Term::Global(name) = slit_ctor.as_ref() {
1007                    if name == "SLit" {
1008                        if let Term::Lit(Literal::Int(m)) = m_val.as_ref() {
1009                            *m
1010                        } else {
1011                            return None;
1012                        }
1013                    } else {
1014                        return None;
1015                    }
1016                } else {
1017                    return None;
1018                }
1019            } else {
1020                return None;
1021            };
1022
1023            // Compute result based on operator
1024            let result = match op.as_str() {
1025                "add" => n.checked_add(m),
1026                "sub" => n.checked_sub(m),
1027                "mul" => n.checked_mul(m),
1028                "div" => {
1029                    if m == 0 {
1030                        return None; // Division by zero: remain stuck
1031                    }
1032                    n.checked_div(m)
1033                }
1034                "mod" => {
1035                    if m == 0 {
1036                        return None; // Mod by zero: remain stuck
1037                    }
1038                    n.checked_rem(m)
1039                }
1040                _ => None,
1041            };
1042
1043            // Build SLit result
1044            if let Some(r) = result {
1045                return Some(Term::App(
1046                    Box::new(Term::Global("SLit".to_string())),
1047                    Box::new(Term::Lit(Literal::Int(r))),
1048                ));
1049            }
1050        }
1051    }
1052
1053    None
1054}
1055
1056/// Single-step head reduction.
1057///
1058/// Looks for the leftmost-outermost beta redex and reduces it.
1059/// - If SApp (SLam T body) arg: perform beta reduction
1060/// - If SApp f x where f is reducible: reduce f
1061/// - Otherwise: return unchanged (stuck or value)
1062fn try_syn_step_reduce(ctx: &Context, term: &Term) -> Option<Term> {
1063    // Check for SApp (binary constructor)
1064    if let Term::App(ctor_term, arg) = term {
1065        if let Term::App(inner, func) = ctor_term.as_ref() {
1066            if let Term::Global(ctor_name) = inner.as_ref() {
1067                if ctor_name == "SApp" {
1068                    // We have SApp func arg
1069                    // First, check for arithmetic primitives: SApp (SApp (SName op) (SLit n)) (SLit m)
1070                    if let Some(result) = try_syn_arith_reduce(func.as_ref(), arg.as_ref()) {
1071                        return Some(result);
1072                    }
1073
1074                    // Check if func is SLam (beta redex)
1075                    if let Term::App(lam_inner, body) = func.as_ref() {
1076                        if let Term::App(lam_ctor, _param_type) = lam_inner.as_ref() {
1077                            if let Term::Global(lam_name) = lam_ctor.as_ref() {
1078                                if lam_name == "SLam" {
1079                                    // Beta redex! (SApp (SLam T body) arg) → syn_beta body arg
1080                                    return try_syn_beta_reduce(ctx, body.as_ref(), arg.as_ref());
1081                                }
1082                            }
1083                        }
1084                    }
1085
1086                    // Not a beta redex. Try to step the function.
1087                    if let Some(stepped_func) = try_syn_step_reduce(ctx, func.as_ref()) {
1088                        // Check if func actually changed
1089                        if &stepped_func != func.as_ref() {
1090                            // func reduced, reconstruct SApp stepped_func arg
1091                            return Some(Term::App(
1092                                Box::new(Term::App(
1093                                    Box::new(Term::Global("SApp".to_string())),
1094                                    Box::new(stepped_func),
1095                                )),
1096                                Box::new(arg.as_ref().clone()),
1097                            ));
1098                        }
1099                    }
1100
1101                    // func is stuck. Try to step the argument.
1102                    if let Some(stepped_arg) = try_syn_step_reduce(ctx, arg.as_ref()) {
1103                        if &stepped_arg != arg.as_ref() {
1104                            // arg reduced, reconstruct SApp func stepped_arg
1105                            return Some(Term::App(
1106                                Box::new(Term::App(
1107                                    Box::new(Term::Global("SApp".to_string())),
1108                                    Box::new(func.as_ref().clone()),
1109                                )),
1110                                Box::new(stepped_arg),
1111                            ));
1112                        }
1113                    }
1114
1115                    // Both func and arg are stuck, return original term
1116                    return Some(term.clone());
1117                }
1118            }
1119        }
1120    }
1121
1122    // Not an application - it's a value or stuck term
1123    // Return unchanged
1124    Some(term.clone())
1125}
1126
1127// -------------------------------------------------------------------------
1128// Bounded Evaluation
1129// -------------------------------------------------------------------------
1130
1131/// Bounded evaluation: reduce for up to N steps.
1132///
1133/// syn_eval fuel term:
1134/// - If fuel <= 0: return term unchanged
1135/// - Otherwise: step and repeat until normal form or fuel exhausted
1136fn try_syn_eval_reduce(ctx: &Context, fuel: i64, term: &Term) -> Option<Term> {
1137    if fuel <= 0 {
1138        return Some(term.clone());
1139    }
1140
1141    // Try one step
1142    let stepped = try_syn_step_reduce(ctx, term)?;
1143
1144    // If term didn't change, it's in normal form (or stuck)
1145    if &stepped == term {
1146        return Some(term.clone());
1147    }
1148
1149    // Continue with reduced fuel
1150    try_syn_eval_reduce(ctx, fuel - 1, &stepped)
1151}
1152
1153// -------------------------------------------------------------------------
1154// Reification (Quote)
1155// -------------------------------------------------------------------------
1156
1157/// Quote a Syntax value: produce Syntax that constructs it.
1158///
1159/// syn_quote term:
1160/// - SVar n → SApp (SName "SVar") (SLit n)
1161/// - SGlobal n → SApp (SName "SGlobal") (SLit n)
1162/// - SSort u → SApp (SName "SSort") (quote_univ u)
1163/// - SApp f x → SApp (SApp (SName "SApp") (quote f)) (quote x)
1164/// - SLam T b → SApp (SApp (SName "SLam") (quote T)) (quote b)
1165/// - SPi T B → SApp (SApp (SName "SPi") (quote T)) (quote B)
1166/// - SLit n → SApp (SName "SLit") (SLit n)
1167/// - SName s → SName s (self-quoting)
1168#[allow(dead_code)]
1169fn try_syn_quote_reduce(ctx: &Context, term: &Term) -> Option<Term> {
1170    // Helper to build SApp (SName name) arg
1171    fn sname_app(name: &str, arg: Term) -> Term {
1172        Term::App(
1173            Box::new(Term::App(
1174                Box::new(Term::Global("SApp".to_string())),
1175                Box::new(Term::App(
1176                    Box::new(Term::Global("SName".to_string())),
1177                    Box::new(Term::Lit(Literal::Text(name.to_string()))),
1178                )),
1179            )),
1180            Box::new(arg),
1181        )
1182    }
1183
1184    // Helper to build SLit n
1185    fn slit(n: i64) -> Term {
1186        Term::App(
1187            Box::new(Term::Global("SLit".to_string())),
1188            Box::new(Term::Lit(Literal::Int(n))),
1189        )
1190    }
1191
1192    // Helper to build SName s
1193    fn sname(s: &str) -> Term {
1194        Term::App(
1195            Box::new(Term::Global("SName".to_string())),
1196            Box::new(Term::Lit(Literal::Text(s.to_string()))),
1197        )
1198    }
1199
1200    // Helper to build SApp (SApp (SName name) a) b
1201    fn sname_app2(name: &str, a: Term, b: Term) -> Term {
1202        Term::App(
1203            Box::new(Term::App(
1204                Box::new(Term::Global("SApp".to_string())),
1205                Box::new(sname_app(name, a)),
1206            )),
1207            Box::new(b),
1208        )
1209    }
1210
1211    // Match on Syntax constructors
1212    if let Term::App(ctor_term, inner_arg) = term {
1213        if let Term::Global(ctor_name) = ctor_term.as_ref() {
1214            match ctor_name.as_str() {
1215                "SVar" => {
1216                    if let Term::Lit(Literal::Int(n)) = inner_arg.as_ref() {
1217                        return Some(sname_app("SVar", slit(*n)));
1218                    }
1219                }
1220                "SGlobal" => {
1221                    if let Term::Lit(Literal::Int(n)) = inner_arg.as_ref() {
1222                        return Some(sname_app("SGlobal", slit(*n)));
1223                    }
1224                }
1225                "SSort" => {
1226                    // Quote the universe
1227                    let quoted_univ = quote_univ(inner_arg)?;
1228                    return Some(sname_app("SSort", quoted_univ));
1229                }
1230                "SLit" => {
1231                    if let Term::Lit(Literal::Int(n)) = inner_arg.as_ref() {
1232                        return Some(sname_app("SLit", slit(*n)));
1233                    }
1234                }
1235                "SName" => {
1236                    // SName is self-quoting
1237                    return Some(term.clone());
1238                }
1239                _ => {}
1240            }
1241        }
1242
1243        // Binary constructors: ((Ctor a) b)
1244        if let Term::App(inner, a) = ctor_term.as_ref() {
1245            if let Term::Global(ctor_name) = inner.as_ref() {
1246                match ctor_name.as_str() {
1247                    "SApp" | "SLam" | "SPi" => {
1248                        let quoted_a = try_syn_quote_reduce(ctx, a)?;
1249                        let quoted_b = try_syn_quote_reduce(ctx, inner_arg)?;
1250                        return Some(sname_app2(ctor_name, quoted_a, quoted_b));
1251                    }
1252                    _ => {}
1253                }
1254            }
1255        }
1256    }
1257
1258    None
1259}
1260
1261/// Quote a Univ value.
1262///
1263/// UProp → SName "UProp"
1264/// UType n → SApp (SName "UType") (SLit n)
1265fn quote_univ(term: &Term) -> Option<Term> {
1266    fn sname(s: &str) -> Term {
1267        Term::App(
1268            Box::new(Term::Global("SName".to_string())),
1269            Box::new(Term::Lit(Literal::Text(s.to_string()))),
1270        )
1271    }
1272
1273    fn slit(n: i64) -> Term {
1274        Term::App(
1275            Box::new(Term::Global("SLit".to_string())),
1276            Box::new(Term::Lit(Literal::Int(n))),
1277        )
1278    }
1279
1280    fn sname_app(name: &str, arg: Term) -> Term {
1281        Term::App(
1282            Box::new(Term::App(
1283                Box::new(Term::Global("SApp".to_string())),
1284                Box::new(sname(name)),
1285            )),
1286            Box::new(arg),
1287        )
1288    }
1289
1290    if let Term::Global(name) = term {
1291        if name == "UProp" {
1292            return Some(sname("UProp"));
1293        }
1294    }
1295
1296    if let Term::App(ctor, arg) = term {
1297        if let Term::Global(name) = ctor.as_ref() {
1298            if name == "UType" {
1299                if let Term::Lit(Literal::Int(n)) = arg.as_ref() {
1300                    return Some(sname_app("UType", slit(*n)));
1301                }
1302            }
1303        }
1304    }
1305
1306    None
1307}
1308
1309// -------------------------------------------------------------------------
1310// Diagonalization (Self-Reference)
1311// -------------------------------------------------------------------------
1312
1313/// The diagonal function: syn_diag x = syn_subst (syn_quote x) 0 x
1314///
1315/// This takes a term x with free variable 0, quotes x to get its construction
1316/// code, then substitutes that code for variable 0 in x.
1317fn try_syn_diag_reduce(ctx: &Context, term: &Term) -> Option<Term> {
1318    // Step 1: Quote the term
1319    let quoted = try_syn_quote_reduce(ctx, term)?;
1320
1321    // Step 2: Substitute the quoted form for variable 0 in the original term
1322    try_syn_subst_reduce(ctx, &quoted, 0, term)
1323}
1324
1325// -------------------------------------------------------------------------
1326// Inference Rules
1327// -------------------------------------------------------------------------
1328
1329/// Extract the conclusion from a derivation.
1330///
1331/// concludes d:
1332/// - DAxiom P → P
1333/// - DModusPonens d_impl d_ant → extract B from (Implies A B) if A matches antecedent
1334/// - DUnivIntro d → wrap conclusion in Forall
1335/// - DUnivElim d term → substitute term into forall body
1336/// - DRefl T a → Eq T a a
1337fn try_concludes_reduce(ctx: &Context, deriv: &Term) -> Option<Term> {
1338    // DAxiom P → P
1339    if let Term::App(ctor_term, p) = deriv {
1340        if let Term::Global(ctor_name) = ctor_term.as_ref() {
1341            if ctor_name == "DAxiom" {
1342                return Some(p.as_ref().clone());
1343            }
1344            if ctor_name == "DUnivIntro" {
1345                // Get conclusion of inner derivation
1346                let inner_conc = try_concludes_reduce(ctx, p)?;
1347                // Lift it by 1 and wrap in Forall
1348                let lifted = try_syn_lift_reduce(ctx, 1, 0, &inner_conc)?;
1349                return Some(make_forall_syntax(&lifted));
1350            }
1351            if ctor_name == "DCompute" {
1352                // DCompute goal: verify goal is Eq T A B and eval(A) == eval(B)
1353                return try_dcompute_conclude(ctx, p);
1354            }
1355            if ctor_name == "DRingSolve" {
1356                // DRingSolve goal: verify goal is Eq T A B and polynomials are equal
1357                return try_dring_solve_conclude(ctx, p);
1358            }
1359            if ctor_name == "DLiaSolve" {
1360                // DLiaSolve goal: verify goal is an inequality and LIA proves it
1361                return try_dlia_solve_conclude(ctx, p);
1362            }
1363            if ctor_name == "DccSolve" {
1364                // DccSolve goal: verify goal by congruence closure
1365                return try_dcc_solve_conclude(ctx, p);
1366            }
1367            if ctor_name == "DSimpSolve" {
1368                // DSimpSolve goal: verify goal by simplification
1369                return try_dsimp_solve_conclude(ctx, p);
1370            }
1371            if ctor_name == "DOmegaSolve" {
1372                // DOmegaSolve goal: verify goal by integer arithmetic
1373                return try_domega_solve_conclude(ctx, p);
1374            }
1375            if ctor_name == "DAutoSolve" {
1376                // DAutoSolve goal: verify goal by trying all tactics
1377                return try_dauto_solve_conclude(ctx, p);
1378            }
1379            if ctor_name == "DInversion" {
1380                // DInversion hyp_type: verify no constructor can match, return False
1381                return try_dinversion_conclude(ctx, p);
1382            }
1383        }
1384    }
1385
1386    // DRewrite eq_proof old_goal new_goal → new_goal (if verified)
1387    // Pattern: App(App(App(DRewrite, eq_proof), old_goal), new_goal)
1388    if let Term::App(partial1, new_goal) = deriv {
1389        if let Term::App(partial2, old_goal) = partial1.as_ref() {
1390            if let Term::App(ctor_term, eq_proof) = partial2.as_ref() {
1391                if let Term::Global(ctor_name) = ctor_term.as_ref() {
1392                    if ctor_name == "DRewrite" {
1393                        return try_drewrite_conclude(ctx, eq_proof, old_goal, new_goal);
1394                    }
1395                }
1396            }
1397        }
1398    }
1399
1400    // DDestruct ind_type motive cases → Forall ind_type motive (if verified)
1401    // Pattern: App(App(App(DDestruct, ind_type), motive), cases)
1402    if let Term::App(partial1, cases) = deriv {
1403        if let Term::App(partial2, motive) = partial1.as_ref() {
1404            if let Term::App(ctor_term, ind_type) = partial2.as_ref() {
1405                if let Term::Global(ctor_name) = ctor_term.as_ref() {
1406                    if ctor_name == "DDestruct" {
1407                        return try_ddestruct_conclude(ctx, ind_type, motive, cases);
1408                    }
1409                }
1410            }
1411        }
1412    }
1413
1414    // DApply hyp_name hyp_proof old_goal new_goal → new_goal (if verified)
1415    // Pattern: App(App(App(App(DApply, hyp_name), hyp_proof), old_goal), new_goal)
1416    if let Term::App(partial1, new_goal) = deriv {
1417        if let Term::App(partial2, old_goal) = partial1.as_ref() {
1418            if let Term::App(partial3, hyp_proof) = partial2.as_ref() {
1419                if let Term::App(ctor_term, hyp_name) = partial3.as_ref() {
1420                    if let Term::Global(ctor_name) = ctor_term.as_ref() {
1421                        if ctor_name == "DApply" {
1422                            return try_dapply_conclude(ctx, hyp_name, hyp_proof, old_goal, new_goal);
1423                        }
1424                    }
1425                }
1426            }
1427        }
1428    }
1429
1430    // DRefl T a → Eq T a a
1431    if let Term::App(partial, a) = deriv {
1432        if let Term::App(ctor_term, t) = partial.as_ref() {
1433            if let Term::Global(ctor_name) = ctor_term.as_ref() {
1434                if ctor_name == "DRefl" {
1435                    return Some(make_eq_syntax(t.as_ref(), a.as_ref()));
1436                }
1437                if ctor_name == "DCong" {
1438                    // DCong context eq_proof → Eq T (f a) (f b)
1439                    // where context = SLam T body, eq_proof proves Eq T a b
1440                    return try_dcong_conclude(ctx, t, a);
1441                }
1442            }
1443        }
1444    }
1445
1446    // DModusPonens d_impl d_ant
1447    if let Term::App(partial, d_ant) = deriv {
1448        if let Term::App(ctor_term, d_impl) = partial.as_ref() {
1449            if let Term::Global(ctor_name) = ctor_term.as_ref() {
1450                if ctor_name == "DModusPonens" {
1451                    // Get conclusions of both derivations
1452                    let impl_conc = try_concludes_reduce(ctx, d_impl)?;
1453                    let ant_conc = try_concludes_reduce(ctx, d_ant)?;
1454
1455                    // Check if impl_conc = SApp (SApp (SName "Implies") A) B
1456                    if let Some((a, b)) = extract_implication(&impl_conc) {
1457                        // Check if ant_conc equals A
1458                        if syntax_equal(&ant_conc, &a) {
1459                            return Some(b);
1460                        }
1461                    }
1462                    // Invalid modus ponens
1463                    return Some(make_sname_error());
1464                }
1465                if ctor_name == "DUnivElim" {
1466                    // d_impl is the derivation, d_ant is the term to substitute
1467                    let conc = try_concludes_reduce(ctx, d_impl)?;
1468                    if let Some(body) = extract_forall_body(&conc) {
1469                        // Substitute d_ant for var 0 in body
1470                        return try_syn_subst_reduce(ctx, d_ant, 0, &body);
1471                    }
1472                    // Invalid universal elimination
1473                    return Some(make_sname_error());
1474                }
1475            }
1476        }
1477    }
1478
1479    // DInduction motive base step → Forall Nat motive (if verified)
1480    // Pattern: App(App(App(DInduction, motive), base), step)
1481    if let Term::App(partial1, step) = deriv {
1482        if let Term::App(partial2, base) = partial1.as_ref() {
1483            if let Term::App(ctor_term, motive) = partial2.as_ref() {
1484                if let Term::Global(ctor_name) = ctor_term.as_ref() {
1485                    if ctor_name == "DInduction" {
1486                        return try_dinduction_reduce(ctx, motive, base, step);
1487                    }
1488                }
1489            }
1490        }
1491    }
1492
1493    // DElim ind_type motive cases → Forall ind_type motive (if verified)
1494    // Pattern: App(App(App(DElim, ind_type), motive), cases)
1495    if let Term::App(partial1, cases) = deriv {
1496        if let Term::App(partial2, motive) = partial1.as_ref() {
1497            if let Term::App(ctor_term, ind_type) = partial2.as_ref() {
1498                if let Term::Global(ctor_name) = ctor_term.as_ref() {
1499                    if ctor_name == "DElim" {
1500                        return try_delim_conclude(ctx, ind_type, motive, cases);
1501                    }
1502                }
1503            }
1504        }
1505    }
1506
1507    None
1508}
1509
1510/// Extract (A, B) from SApp (SApp (SName "Implies") A) B
1511///
1512/// In kernel representation:
1513/// SApp X Y = App(App(SApp, X), Y)
1514/// So SApp (SApp (SName "Implies") A) B =
1515///   App(App(SApp, App(App(SApp, App(SName, "Implies")), A)), B)
1516fn extract_implication(term: &Term) -> Option<(Term, Term)> {
1517    // term = App(App(SApp, X), B)
1518    if let Term::App(outer, b) = term {
1519        if let Term::App(sapp_outer, x) = outer.as_ref() {
1520            if let Term::Global(ctor) = sapp_outer.as_ref() {
1521                if ctor == "SApp" {
1522                    // x = App(App(SApp, App(SName, "Implies")), A)
1523                    if let Term::App(inner, a) = x.as_ref() {
1524                        if let Term::App(sapp_inner, sname_implies) = inner.as_ref() {
1525                            if let Term::Global(ctor2) = sapp_inner.as_ref() {
1526                                if ctor2 == "SApp" {
1527                                    // sname_implies = App(SName, "Implies")
1528                                    if let Term::App(sname, text) = sname_implies.as_ref() {
1529                                        if let Term::Global(sname_ctor) = sname.as_ref() {
1530                                            if sname_ctor == "SName" {
1531                                                if let Term::Lit(Literal::Text(s)) = text.as_ref() {
1532                                                    if s == "Implies" || s == "implies" {
1533                                                        return Some((
1534                                                            a.as_ref().clone(),
1535                                                            b.as_ref().clone(),
1536                                                        ));
1537                                                    }
1538                                                }
1539                                            }
1540                                        }
1541                                    }
1542                                }
1543                            }
1544                        }
1545                    }
1546                }
1547            }
1548        }
1549    }
1550    None
1551}
1552
1553/// Extract all hypotheses from a chain of nested implications.
1554///
1555/// For `A -> B -> C`, returns `([A, B], C)`.
1556/// For non-implications, returns `([], term)`.
1557fn extract_implications(term: &Term) -> Option<(Vec<Term>, Term)> {
1558    let mut hyps = Vec::new();
1559    let mut current = term.clone();
1560
1561    while let Some((hyp, rest)) = extract_implication(&current) {
1562        hyps.push(hyp);
1563        current = rest;
1564    }
1565
1566    if hyps.is_empty() {
1567        None
1568    } else {
1569        Some((hyps, current))
1570    }
1571}
1572
1573/// Extract body from SApp (SApp (SName "Forall") T) (SLam T body)
1574///
1575/// In kernel representation:
1576/// Extract body from Forall syntax (two forms supported):
1577/// Form 1: SApp (SName "Forall") (SLam T body)
1578/// Form 2: SApp (SApp (SName "Forall") T) (SLam T body)
1579fn extract_forall_body(term: &Term) -> Option<Term> {
1580    // term = App(App(SApp, X), lam)
1581    if let Term::App(outer, lam) = term {
1582        if let Term::App(sapp_outer, x) = outer.as_ref() {
1583            if let Term::Global(ctor) = sapp_outer.as_ref() {
1584                if ctor == "SApp" {
1585                    // Form 1: X = App(SName, "Forall")
1586                    if let Term::App(sname, text) = x.as_ref() {
1587                        if let Term::Global(sname_ctor) = sname.as_ref() {
1588                            if sname_ctor == "SName" {
1589                                if let Term::Lit(Literal::Text(s)) = text.as_ref() {
1590                                    if s == "Forall" {
1591                                        // Extract body from SLam
1592                                        return extract_slam_body(lam);
1593                                    }
1594                                }
1595                            }
1596                        }
1597                    }
1598
1599                    // Form 2: X = App(App(SApp, App(SName, "Forall")), T)
1600                    if let Term::App(inner, _t) = x.as_ref() {
1601                        if let Term::App(sapp_inner, sname_forall) = inner.as_ref() {
1602                            if let Term::Global(ctor2) = sapp_inner.as_ref() {
1603                                if ctor2 == "SApp" {
1604                                    if let Term::App(sname, text) = sname_forall.as_ref() {
1605                                        if let Term::Global(sname_ctor) = sname.as_ref() {
1606                                            if sname_ctor == "SName" {
1607                                                if let Term::Lit(Literal::Text(s)) = text.as_ref() {
1608                                                    if s == "Forall" {
1609                                                        return extract_slam_body(lam);
1610                                                    }
1611                                                }
1612                                            }
1613                                        }
1614                                    }
1615                                }
1616                            }
1617                        }
1618                    }
1619                }
1620            }
1621        }
1622    }
1623    None
1624}
1625
1626/// Extract body from ((SLam T) body)
1627fn extract_slam_body(term: &Term) -> Option<Term> {
1628    if let Term::App(inner, body) = term {
1629        if let Term::App(slam, _t) = inner.as_ref() {
1630            if let Term::Global(name) = slam.as_ref() {
1631                if name == "SLam" {
1632                    return Some(body.as_ref().clone());
1633                }
1634            }
1635        }
1636    }
1637    None
1638}
1639
1640/// Structural equality check for Syntax terms
1641fn syntax_equal(a: &Term, b: &Term) -> bool {
1642    a == b
1643}
1644
1645/// Build SName "Error"
1646fn make_sname_error() -> Term {
1647    Term::App(
1648        Box::new(Term::Global("SName".to_string())),
1649        Box::new(Term::Lit(Literal::Text("Error".to_string()))),
1650    )
1651}
1652
1653/// Build SName from a string
1654fn make_sname(s: &str) -> Term {
1655    Term::App(
1656        Box::new(Term::Global("SName".to_string())),
1657        Box::new(Term::Lit(Literal::Text(s.to_string()))),
1658    )
1659}
1660
1661/// Build SLit from an integer
1662fn make_slit(n: i64) -> Term {
1663    Term::App(
1664        Box::new(Term::Global("SLit".to_string())),
1665        Box::new(Term::Lit(Literal::Int(n))),
1666    )
1667}
1668
1669/// Build SApp f x
1670fn make_sapp(f: Term, x: Term) -> Term {
1671    Term::App(
1672        Box::new(Term::App(
1673            Box::new(Term::Global("SApp".to_string())),
1674            Box::new(f),
1675        )),
1676        Box::new(x),
1677    )
1678}
1679
1680/// Build SPi A B
1681fn make_spi(a: Term, b: Term) -> Term {
1682    Term::App(
1683        Box::new(Term::App(
1684            Box::new(Term::Global("SPi".to_string())),
1685            Box::new(a),
1686        )),
1687        Box::new(b),
1688    )
1689}
1690
1691/// Build SLam A b
1692fn make_slam(a: Term, b: Term) -> Term {
1693    Term::App(
1694        Box::new(Term::App(
1695            Box::new(Term::Global("SLam".to_string())),
1696            Box::new(a),
1697        )),
1698        Box::new(b),
1699    )
1700}
1701
1702/// Build SSort u
1703fn make_ssort(u: Term) -> Term {
1704    Term::App(
1705        Box::new(Term::Global("SSort".to_string())),
1706        Box::new(u),
1707    )
1708}
1709
1710/// Build SVar n
1711fn make_svar(n: i64) -> Term {
1712    Term::App(
1713        Box::new(Term::Global("SVar".to_string())),
1714        Box::new(Term::Lit(Literal::Int(n))),
1715    )
1716}
1717
1718/// Convert a kernel Term to Syntax encoding (S* form).
1719///
1720/// This converts semantic Terms to their syntactic representation:
1721/// - Global(n) → SName n
1722/// - Var(n) → SName n (for named variables)
1723/// - App(f, x) → SApp (convert f) (convert x)
1724/// - Pi { param, param_type, body_type } → SPi (convert param_type) (convert body_type)
1725/// - Lambda { param, param_type, body } → SLam (convert param_type) (convert body)
1726/// - Sort(Type n) → SSort (UType n)
1727/// - Sort(Prop) → SSort UProp
1728/// - Lit(Int n) → SLit n
1729/// - Lit(Text s) → SName s
1730fn term_to_syntax(term: &Term) -> Option<Term> {
1731    match term {
1732        Term::Global(name) => Some(make_sname(name)),
1733
1734        Term::Var(name) => {
1735            // Named variables become SName in syntax representation
1736            Some(make_sname(name))
1737        }
1738
1739        Term::App(f, x) => {
1740            let sf = term_to_syntax(f)?;
1741            let sx = term_to_syntax(x)?;
1742            Some(make_sapp(sf, sx))
1743        }
1744
1745        Term::Pi { param_type, body_type, .. } => {
1746            let sp = term_to_syntax(param_type)?;
1747            let sb = term_to_syntax(body_type)?;
1748            Some(make_spi(sp, sb))
1749        }
1750
1751        Term::Lambda { param_type, body, .. } => {
1752            let sp = term_to_syntax(param_type)?;
1753            let sb = term_to_syntax(body)?;
1754            Some(make_slam(sp, sb))
1755        }
1756
1757        Term::Sort(Universe::Type(n)) => {
1758            let u = Term::App(
1759                Box::new(Term::Global("UType".to_string())),
1760                Box::new(Term::Lit(Literal::Int(*n as i64))),
1761            );
1762            Some(make_ssort(u))
1763        }
1764
1765        Term::Sort(Universe::Prop) => {
1766            let u = Term::Global("UProp".to_string());
1767            Some(make_ssort(u))
1768        }
1769
1770        Term::Lit(Literal::Int(n)) => Some(make_slit(*n)),
1771
1772        Term::Lit(Literal::Text(s)) => Some(make_sname(s)),
1773
1774        // Float, Duration, Date, and Moment literals - skip for now as they're rarely used in proofs
1775        Term::Lit(Literal::Float(_))
1776        | Term::Lit(Literal::Duration(_))
1777        | Term::Lit(Literal::Date(_))
1778        | Term::Lit(Literal::Moment(_)) => None,
1779
1780        // Match expressions, Fix, and Hole are complex - skip for now
1781        Term::Match { .. } | Term::Fix { .. } | Term::Hole => None,
1782    }
1783}
1784
1785/// Build DHint derivation that references a hint by name.
1786///
1787/// Uses DAxiom (SName "hint_name") to indicate the proof uses this hint.
1788fn make_hint_derivation(hint_name: &str, goal: &Term) -> Term {
1789    // Build: DAxiom (SApp (SName "Hint") (SName hint_name))
1790    // This distinguishes hints from error derivations
1791    let hint_marker = make_sapp(make_sname("Hint"), make_sname(hint_name));
1792
1793    // Actually, let's use a simpler approach: DAutoSolve goal
1794    // This indicates auto succeeded, and we can trace which hint was used through debugging
1795    Term::App(
1796        Box::new(Term::Global("DAutoSolve".to_string())),
1797        Box::new(goal.clone()),
1798    )
1799}
1800
1801/// Try to apply a hint to prove a goal.
1802///
1803/// Returns Some(derivation) if the hint's type matches the goal,
1804/// otherwise returns None.
1805fn try_apply_hint(ctx: &Context, hint_name: &str, hint_type: &Term, goal: &Term) -> Option<Term> {
1806    // Convert hint type to syntax form
1807    let hint_syntax = term_to_syntax(hint_type)?;
1808
1809    // Normalize both for comparison
1810    let norm_hint = normalize(ctx, &hint_syntax);
1811    let norm_goal = normalize(ctx, goal);
1812
1813    // Direct match: hint type equals goal
1814    if syntax_equal(&norm_hint, &norm_goal) {
1815        return Some(make_hint_derivation(hint_name, goal));
1816    }
1817
1818    // Try backward chaining for implications: if hint is P → Q and goal is Q,
1819    // try to prove P using auto and then apply the hint
1820    if let Term::App(outer, q) = &hint_syntax {
1821        if let Term::App(pi_ctor, p) = outer.as_ref() {
1822            if let Term::Global(name) = pi_ctor.as_ref() {
1823                if name == "SPi" {
1824                    // hint_type is SPi P Q, goal might be Q
1825                    let norm_q = normalize(ctx, q);
1826                    if syntax_equal(&norm_q, &norm_goal) {
1827                        // Need to prove P first, then apply hint
1828                        // This would require recursive auto call - skip for now
1829                        // to avoid infinite recursion
1830                    }
1831                }
1832            }
1833        }
1834    }
1835
1836    None
1837}
1838
1839/// Build Forall Type0 (SLam Type0 body)
1840fn make_forall_syntax(body: &Term) -> Term {
1841    let type0 = Term::App(
1842        Box::new(Term::Global("SSort".to_string())),
1843        Box::new(Term::App(
1844            Box::new(Term::Global("UType".to_string())),
1845            Box::new(Term::Lit(Literal::Int(0))),
1846        )),
1847    );
1848
1849    // SLam Type0 body
1850    let slam = Term::App(
1851        Box::new(Term::App(
1852            Box::new(Term::Global("SLam".to_string())),
1853            Box::new(type0.clone()),
1854        )),
1855        Box::new(body.clone()),
1856    );
1857
1858    // SApp (SApp (SName "Forall") Type0) slam
1859    Term::App(
1860        Box::new(Term::App(
1861            Box::new(Term::Global("SApp".to_string())),
1862            Box::new(Term::App(
1863                Box::new(Term::App(
1864                    Box::new(Term::Global("SApp".to_string())),
1865                    Box::new(Term::App(
1866                        Box::new(Term::Global("SName".to_string())),
1867                        Box::new(Term::Lit(Literal::Text("Forall".to_string()))),
1868                    )),
1869                )),
1870                Box::new(type0),
1871            )),
1872        )),
1873        Box::new(slam),
1874    )
1875}
1876
1877// -------------------------------------------------------------------------
1878// Core Tactics
1879// -------------------------------------------------------------------------
1880
1881/// Build SApp (SApp (SApp (SName "Eq") type_s) term) term
1882///
1883/// Constructs the Syntax representation of (Eq type_s term term)
1884fn make_eq_syntax(type_s: &Term, term: &Term) -> Term {
1885    let eq_name = Term::App(
1886        Box::new(Term::Global("SName".to_string())),
1887        Box::new(Term::Lit(Literal::Text("Eq".to_string()))),
1888    );
1889
1890    // SApp (SName "Eq") type_s
1891    let app1 = Term::App(
1892        Box::new(Term::App(
1893            Box::new(Term::Global("SApp".to_string())),
1894            Box::new(eq_name),
1895        )),
1896        Box::new(type_s.clone()),
1897    );
1898
1899    // SApp (SApp (SName "Eq") type_s) term
1900    let app2 = Term::App(
1901        Box::new(Term::App(
1902            Box::new(Term::Global("SApp".to_string())),
1903            Box::new(app1),
1904        )),
1905        Box::new(term.clone()),
1906    );
1907
1908    // SApp (SApp (SApp (SName "Eq") type_s) term) term
1909    Term::App(
1910        Box::new(Term::App(
1911            Box::new(Term::Global("SApp".to_string())),
1912            Box::new(app2),
1913        )),
1914        Box::new(term.clone()),
1915    )
1916}
1917
1918/// Extract (T, a, b) from SApp (SApp (SApp (SName "Eq") T) a) b
1919///
1920/// Pattern matches against the Syntax representation of (Eq T a b)
1921fn extract_eq(term: &Term) -> Option<(Term, Term, Term)> {
1922    // term = App(App(SApp, X), b)
1923    if let Term::App(outer, b) = term {
1924        if let Term::App(sapp_outer, x) = outer.as_ref() {
1925            if let Term::Global(ctor) = sapp_outer.as_ref() {
1926                if ctor == "SApp" {
1927                    // x = App(App(SApp, Y), a)
1928                    if let Term::App(inner, a) = x.as_ref() {
1929                        if let Term::App(sapp_inner, y) = inner.as_ref() {
1930                            if let Term::Global(ctor2) = sapp_inner.as_ref() {
1931                                if ctor2 == "SApp" {
1932                                    // y = App(App(SApp, eq_name), t)
1933                                    if let Term::App(inner2, t) = y.as_ref() {
1934                                        if let Term::App(sapp_inner2, sname_eq) = inner2.as_ref() {
1935                                            if let Term::Global(ctor3) = sapp_inner2.as_ref() {
1936                                                if ctor3 == "SApp" {
1937                                                    // sname_eq = App(SName, "Eq")
1938                                                    if let Term::App(sname, text) = sname_eq.as_ref()
1939                                                    {
1940                                                        if let Term::Global(sname_ctor) =
1941                                                            sname.as_ref()
1942                                                        {
1943                                                            if sname_ctor == "SName" {
1944                                                                if let Term::Lit(Literal::Text(s)) =
1945                                                                    text.as_ref()
1946                                                                {
1947                                                                    if s == "Eq" {
1948                                                                        return Some((
1949                                                                            t.as_ref().clone(),
1950                                                                            a.as_ref().clone(),
1951                                                                            b.as_ref().clone(),
1952                                                                        ));
1953                                                                    }
1954                                                                }
1955                                                            }
1956                                                        }
1957                                                    }
1958                                                }
1959                                            }
1960                                        }
1961                                    }
1962                                }
1963                            }
1964                        }
1965                    }
1966                }
1967            }
1968        }
1969    }
1970    None
1971}
1972
1973/// Build DRefl type_s term
1974fn make_drefl(type_s: &Term, term: &Term) -> Term {
1975    let drefl = Term::Global("DRefl".to_string());
1976    let app1 = Term::App(Box::new(drefl), Box::new(type_s.clone()));
1977    Term::App(Box::new(app1), Box::new(term.clone()))
1978}
1979
1980/// Build DAxiom (SName "Error")
1981fn make_error_derivation() -> Term {
1982    let daxiom = Term::Global("DAxiom".to_string());
1983    let error = make_sname_error();
1984    Term::App(Box::new(daxiom), Box::new(error))
1985}
1986
1987/// Build DAxiom goal (identity derivation)
1988fn make_daxiom(goal: &Term) -> Term {
1989    let daxiom = Term::Global("DAxiom".to_string());
1990    Term::App(Box::new(daxiom), Box::new(goal.clone()))
1991}
1992
1993/// Reflexivity tactic: try to prove a goal by reflexivity.
1994///
1995/// try_refl goal:
1996/// - If goal matches (Eq T a b) and a == b, return DRefl T a
1997/// - Otherwise return DAxiom (SName "Error")
1998fn try_try_refl_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
1999    // Normalize the goal first
2000    let norm_goal = normalize(ctx, goal);
2001
2002    // Pattern match: SApp (SApp (SApp (SName "Eq") T) a) b
2003    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2004        // Check if left == right (structural equality)
2005        if syntax_equal(&left, &right) {
2006            // Success! Return DRefl T a
2007            return Some(make_drefl(&type_s, &left));
2008        }
2009    }
2010
2011    // Failure: return error derivation
2012    Some(make_error_derivation())
2013}
2014
2015// =============================================================================
2016// RING TACTIC (POLYNOMIAL EQUALITY)
2017// =============================================================================
2018
2019use crate::ring;
2020use crate::lia;
2021use crate::cc;
2022use crate::simp;
2023
2024/// Ring tactic: try to prove a goal by polynomial normalization.
2025///
2026/// try_ring goal:
2027/// - If goal matches (Eq T a b) where T is Int or Nat
2028/// - And poly(a) == poly(b) after normalization
2029/// - Returns DRingSolve goal
2030/// - Otherwise returns DAxiom (SName "Error")
2031fn try_try_ring_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2032    // Normalize the goal first
2033    let norm_goal = normalize(ctx, goal);
2034
2035    // Pattern match: SApp (SApp (SApp (SName "Eq") T) a) b
2036    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2037        // Check type is Int or Nat (ring types)
2038        if !is_ring_type(&type_s) {
2039            return Some(make_error_derivation());
2040        }
2041
2042        // Reify both sides to polynomials
2043        let poly_left = match ring::reify(&left) {
2044            Ok(p) => p,
2045            Err(_) => return Some(make_error_derivation()),
2046        };
2047        let poly_right = match ring::reify(&right) {
2048            Ok(p) => p,
2049            Err(_) => return Some(make_error_derivation()),
2050        };
2051
2052        // Check canonical equality
2053        if poly_left.canonical_eq(&poly_right) {
2054            // Success! Return DRingSolve goal
2055            return Some(Term::App(
2056                Box::new(Term::Global("DRingSolve".to_string())),
2057                Box::new(norm_goal),
2058            ));
2059        }
2060    }
2061
2062    // Failure: return error derivation
2063    Some(make_error_derivation())
2064}
2065
2066/// Verify DRingSolve proof.
2067///
2068/// DRingSolve goal → goal (if verified)
2069fn try_dring_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2070    let norm_goal = normalize(ctx, goal);
2071
2072    // Extract T, lhs, rhs from Eq T lhs rhs
2073    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2074        // Verify type is a ring type
2075        if !is_ring_type(&type_s) {
2076            return Some(make_sname_error());
2077        }
2078
2079        // Reify and verify
2080        let poly_left = match ring::reify(&left) {
2081            Ok(p) => p,
2082            Err(_) => return Some(make_sname_error()),
2083        };
2084        let poly_right = match ring::reify(&right) {
2085            Ok(p) => p,
2086            Err(_) => return Some(make_sname_error()),
2087        };
2088
2089        if poly_left.canonical_eq(&poly_right) {
2090            return Some(norm_goal);
2091        }
2092    }
2093
2094    Some(make_sname_error())
2095}
2096
2097/// Check if a Syntax type is a ring type (Int or Nat)
2098fn is_ring_type(type_term: &Term) -> bool {
2099    if let Some(name) = extract_sname_from_syntax(type_term) {
2100        return name == "Int" || name == "Nat";
2101    }
2102    false
2103}
2104
2105/// Extract name from SName term
2106fn extract_sname_from_syntax(term: &Term) -> Option<String> {
2107    if let Term::App(ctor, arg) = term {
2108        if let Term::Global(name) = ctor.as_ref() {
2109            if name == "SName" {
2110                if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
2111                    return Some(s.clone());
2112                }
2113            }
2114        }
2115    }
2116    None
2117}
2118
2119// =============================================================================
2120// LIA TACTIC (LINEAR INTEGER ARITHMETIC)
2121// =============================================================================
2122
2123/// LIA tactic: try to prove a goal by Fourier-Motzkin elimination.
2124///
2125/// try_lia goal:
2126/// - If goal matches (Lt/Le/Gt/Ge a b)
2127/// - And Fourier-Motzkin shows the negation is unsatisfiable
2128/// - Returns DLiaSolve goal
2129/// - Otherwise returns DAxiom (SName "Error")
2130fn try_try_lia_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2131    // Normalize the goal first
2132    let norm_goal = normalize(ctx, goal);
2133
2134    // Extract comparison: (SApp (SApp (SName "Lt"|"Le"|etc) a) b)
2135    if let Some((rel, lhs_term, rhs_term)) = lia::extract_comparison(&norm_goal) {
2136        // Reify both sides to linear expressions
2137        let lhs = match lia::reify_linear(&lhs_term) {
2138            Ok(l) => l,
2139            Err(_) => return Some(make_error_derivation()),
2140        };
2141        let rhs = match lia::reify_linear(&rhs_term) {
2142            Ok(r) => r,
2143            Err(_) => return Some(make_error_derivation()),
2144        };
2145
2146        // Convert to negated constraint for validity checking
2147        if let Some(negated) = lia::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2148            // If the negation is unsatisfiable, the goal is valid
2149            if lia::fourier_motzkin_unsat(&[negated]) {
2150                // Success! Return DLiaSolve goal
2151                return Some(Term::App(
2152                    Box::new(Term::Global("DLiaSolve".to_string())),
2153                    Box::new(norm_goal),
2154                ));
2155            }
2156        }
2157    }
2158
2159    // Failure: return error derivation
2160    Some(make_error_derivation())
2161}
2162
2163/// Verify DLiaSolve proof.
2164///
2165/// DLiaSolve goal → goal (if verified)
2166fn try_dlia_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2167    let norm_goal = normalize(ctx, goal);
2168
2169    // Extract comparison and verify
2170    if let Some((rel, lhs_term, rhs_term)) = lia::extract_comparison(&norm_goal) {
2171        // Reify both sides
2172        let lhs = match lia::reify_linear(&lhs_term) {
2173            Ok(l) => l,
2174            Err(_) => return Some(make_sname_error()),
2175        };
2176        let rhs = match lia::reify_linear(&rhs_term) {
2177            Ok(r) => r,
2178            Err(_) => return Some(make_sname_error()),
2179        };
2180
2181        // Verify via Fourier-Motzkin
2182        if let Some(negated) = lia::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2183            if lia::fourier_motzkin_unsat(&[negated]) {
2184                return Some(norm_goal);
2185            }
2186        }
2187    }
2188
2189    Some(make_sname_error())
2190}
2191
2192// =============================================================================
2193// CONGRUENCE CLOSURE TACTIC
2194// =============================================================================
2195
2196/// CC tactic: try to prove a goal by congruence closure.
2197///
2198/// try_cc goal:
2199/// - If goal is a direct equality (Eq a b) or an implication with hypotheses
2200/// - And congruence closure shows the conclusion follows
2201/// - Returns DccSolve goal
2202/// - Otherwise returns DAxiom (SName "Error")
2203fn try_try_cc_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2204    // Normalize the goal first
2205    let norm_goal = normalize(ctx, goal);
2206
2207    // Use cc::check_goal which handles both:
2208    // - Direct equalities: (Eq a b)
2209    // - Implications: (implies (Eq x y) (Eq (f x) (f y)))
2210    if cc::check_goal(&norm_goal) {
2211        // Success! Return DccSolve goal
2212        return Some(Term::App(
2213            Box::new(Term::Global("DccSolve".to_string())),
2214            Box::new(norm_goal),
2215        ));
2216    }
2217
2218    // Failure: return error derivation
2219    Some(make_error_derivation())
2220}
2221
2222/// Verify DccSolve proof.
2223///
2224/// DccSolve goal → goal (if verified)
2225fn try_dcc_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2226    let norm_goal = normalize(ctx, goal);
2227
2228    // Re-verify using congruence closure
2229    if cc::check_goal(&norm_goal) {
2230        return Some(norm_goal);
2231    }
2232
2233    Some(make_sname_error())
2234}
2235
2236// =============================================================================
2237// SIMP TACTIC (SIMPLIFICATION)
2238// =============================================================================
2239
2240/// try_simp goal:
2241/// - Prove equalities by simplification and arithmetic evaluation
2242/// - Handles hypotheses via implications: (implies (Eq x 0) (Eq (add x 1) 1))
2243/// - Returns DSimpSolve goal on success
2244/// - Otherwise returns DAxiom (SName "Error")
2245fn try_try_simp_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2246    // Normalize the goal first
2247    let norm_goal = normalize(ctx, goal);
2248
2249    // Use simp::check_goal which handles:
2250    // - Reflexive equalities: (Eq a a)
2251    // - Constant folding: (Eq (add 2 3) 5)
2252    // - Hypothesis substitution: (implies (Eq x 0) (Eq (add x 1) 1))
2253    if simp::check_goal(&norm_goal) {
2254        // Success! Return DSimpSolve goal
2255        return Some(Term::App(
2256            Box::new(Term::Global("DSimpSolve".to_string())),
2257            Box::new(norm_goal),
2258        ));
2259    }
2260
2261    // Failure: return error derivation
2262    Some(make_error_derivation())
2263}
2264
2265/// Verify DSimpSolve proof.
2266///
2267/// DSimpSolve goal → goal (if verified)
2268fn try_dsimp_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2269    let norm_goal = normalize(ctx, goal);
2270
2271    // Re-verify using simplification
2272    if simp::check_goal(&norm_goal) {
2273        return Some(norm_goal);
2274    }
2275
2276    Some(make_sname_error())
2277}
2278
2279// =============================================================================
2280// OMEGA TACTIC (TRUE INTEGER ARITHMETIC)
2281// =============================================================================
2282
2283/// try_omega goal:
2284///
2285/// Omega tactic using integer-aware Fourier-Motzkin elimination.
2286/// Unlike lia (which uses rationals), omega handles integers properly:
2287/// - x > 1 means x >= 2 (strict-to-nonstrict conversion)
2288/// - 3x <= 10 means x <= 3 (floor division)
2289///
2290/// - Extracts comparison (Lt, Le, Gt, Ge) from goal
2291/// - Reifies to integer linear expressions
2292/// - Converts to negated constraint (validity = negation is unsat)
2293/// - Applies omega test
2294/// - Returns DOmegaSolve goal on success
2295fn try_try_omega_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2296    let norm_goal = normalize(ctx, goal);
2297
2298    // Handle implications: extract hypotheses and check conclusion
2299    if let Some((hyps, conclusion)) = extract_implications(&norm_goal) {
2300        // Convert hypotheses to constraints
2301        let mut constraints = Vec::new();
2302
2303        for hyp in &hyps {
2304            // Try to extract a comparison from the hypothesis
2305            if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(hyp) {
2306                if let (Some(lhs), Some(rhs)) =
2307                    (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2308                {
2309                    // Add hypothesis as a constraint (it's given, so it's a fact)
2310                    // For Lt(a, b): a - b < 0, i.e., (a - b) < 0
2311                    // Constraint form: (a - b + 1) <= 0 for integers (since a < b means a <= b - 1)
2312                    match rel.as_str() {
2313                        "Lt" | "lt" => {
2314                            // a < b means a - b <= -1, i.e., (a - b + 1) <= 0
2315                            let mut expr = lhs.sub(&rhs);
2316                            expr.constant += 1;
2317                            constraints.push(omega::IntConstraint { expr, strict: false });
2318                        }
2319                        "Le" | "le" => {
2320                            // a <= b means a - b <= 0
2321                            constraints.push(omega::IntConstraint {
2322                                expr: lhs.sub(&rhs),
2323                                strict: false,
2324                            });
2325                        }
2326                        "Gt" | "gt" => {
2327                            // a > b means a - b >= 1, i.e., (b - a + 1) <= 0
2328                            let mut expr = rhs.sub(&lhs);
2329                            expr.constant += 1;
2330                            constraints.push(omega::IntConstraint { expr, strict: false });
2331                        }
2332                        "Ge" | "ge" => {
2333                            // a >= b means b - a <= 0
2334                            constraints.push(omega::IntConstraint {
2335                                expr: rhs.sub(&lhs),
2336                                strict: false,
2337                            });
2338                        }
2339                        _ => {}
2340                    }
2341                }
2342            }
2343        }
2344
2345        // Now check if the conclusion is provable given the constraints
2346        if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&conclusion) {
2347            if let (Some(lhs), Some(rhs)) =
2348                (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2349            {
2350                // To prove the conclusion, check if its negation is unsat
2351                if let Some(neg_constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2352                    let mut all_constraints = constraints;
2353                    all_constraints.push(neg_constraint);
2354
2355                    if omega::omega_unsat(&all_constraints) {
2356                        return Some(Term::App(
2357                            Box::new(Term::Global("DOmegaSolve".to_string())),
2358                            Box::new(norm_goal),
2359                        ));
2360                    }
2361                }
2362            }
2363        }
2364
2365        // Failure
2366        return Some(make_error_derivation());
2367    }
2368
2369    // Direct comparison (no hypotheses)
2370    if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&norm_goal) {
2371        if let (Some(lhs), Some(rhs)) =
2372            (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2373        {
2374            if let Some(constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2375                if omega::omega_unsat(&[constraint]) {
2376                    return Some(Term::App(
2377                        Box::new(Term::Global("DOmegaSolve".to_string())),
2378                        Box::new(norm_goal),
2379                    ));
2380                }
2381            }
2382        }
2383    }
2384
2385    Some(make_error_derivation())
2386}
2387
2388/// Verify DOmegaSolve proof.
2389///
2390/// DOmegaSolve goal → goal (if verified)
2391fn try_domega_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2392    let norm_goal = normalize(ctx, goal);
2393
2394    // Re-verify using omega test
2395    // Handle implications
2396    if let Some((hyps, conclusion)) = extract_implications(&norm_goal) {
2397        let mut constraints = Vec::new();
2398
2399        for hyp in &hyps {
2400            if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(hyp) {
2401                if let (Some(lhs), Some(rhs)) =
2402                    (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2403                {
2404                    match rel.as_str() {
2405                        "Lt" | "lt" => {
2406                            let mut expr = lhs.sub(&rhs);
2407                            expr.constant += 1;
2408                            constraints.push(omega::IntConstraint { expr, strict: false });
2409                        }
2410                        "Le" | "le" => {
2411                            constraints.push(omega::IntConstraint {
2412                                expr: lhs.sub(&rhs),
2413                                strict: false,
2414                            });
2415                        }
2416                        "Gt" | "gt" => {
2417                            let mut expr = rhs.sub(&lhs);
2418                            expr.constant += 1;
2419                            constraints.push(omega::IntConstraint { expr, strict: false });
2420                        }
2421                        "Ge" | "ge" => {
2422                            constraints.push(omega::IntConstraint {
2423                                expr: rhs.sub(&lhs),
2424                                strict: false,
2425                            });
2426                        }
2427                        _ => {}
2428                    }
2429                }
2430            }
2431        }
2432
2433        if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&conclusion) {
2434            if let (Some(lhs), Some(rhs)) =
2435                (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2436            {
2437                if let Some(neg_constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2438                    let mut all_constraints = constraints;
2439                    all_constraints.push(neg_constraint);
2440
2441                    if omega::omega_unsat(&all_constraints) {
2442                        return Some(norm_goal);
2443                    }
2444                }
2445            }
2446        }
2447
2448        return Some(make_sname_error());
2449    }
2450
2451    // Direct comparison
2452    if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&norm_goal) {
2453        if let (Some(lhs), Some(rhs)) =
2454            (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2455        {
2456            if let Some(constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2457                if omega::omega_unsat(&[constraint]) {
2458                    return Some(norm_goal);
2459                }
2460            }
2461        }
2462    }
2463
2464    Some(make_sname_error())
2465}
2466
2467// =============================================================================
2468// AUTO TACTIC (THE INFINITY GAUNTLET)
2469// =============================================================================
2470
2471/// Check if a derivation is an error derivation.
2472///
2473/// Error derivations have the pattern: DAxiom (SApp (SName "Error") ...)
2474fn is_error_derivation(term: &Term) -> bool {
2475    // Check for DAxiom constructor
2476    if let Term::App(ctor, arg) = term {
2477        if let Term::Global(name) = ctor.as_ref() {
2478            if name == "DAxiom" {
2479                // Check if arg is SName "Error" or SApp (SName "Error") ...
2480                if let Term::App(sname, inner) = arg.as_ref() {
2481                    if let Term::Global(sn) = sname.as_ref() {
2482                        if sn == "SName" {
2483                            if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2484                                return s == "Error";
2485                            }
2486                        }
2487                        if sn == "SApp" {
2488                            // Recursively check - could be SApp (SName "Error") ...
2489                            return true; // For now, treat any DAxiom as potential error
2490                        }
2491                    }
2492                }
2493                // DAxiom with SName "Error"
2494                if let Term::Global(sn) = arg.as_ref() {
2495                    // This shouldn't happen but just in case
2496                    return sn == "Error";
2497                }
2498                return true; // Any DAxiom is treated as error for auto purposes
2499            }
2500        }
2501    }
2502    false
2503}
2504
2505/// Auto tactic: tries each decision procedure in sequence.
2506///
2507/// Order: True/False → simp → ring → cc → omega → lia
2508/// Returns the first successful derivation, or error if all fail.
2509fn try_try_auto_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2510    let norm_goal = normalize(ctx, goal);
2511
2512    // Handle trivial cases: True and False
2513    // SName "True" is trivially provable
2514    if let Term::App(ctor, inner) = &norm_goal {
2515        if let Term::Global(name) = ctor.as_ref() {
2516            if name == "SName" {
2517                if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2518                    if s == "True" {
2519                        // True is always provable - use DAutoSolve
2520                        return Some(Term::App(
2521                            Box::new(Term::Global("DAutoSolve".to_string())),
2522                            Box::new(norm_goal),
2523                        ));
2524                    }
2525                    if s == "False" {
2526                        // False is never provable
2527                        return Some(make_error_derivation());
2528                    }
2529                }
2530            }
2531        }
2532    }
2533
2534    // Try simp (handles equalities with simplification)
2535    if let Some(result) = try_try_simp_reduce(ctx, &norm_goal) {
2536        if !is_error_derivation(&result) {
2537            return Some(result);
2538        }
2539    }
2540
2541    // Try ring (polynomial equalities)
2542    if let Some(result) = try_try_ring_reduce(ctx, &norm_goal) {
2543        if !is_error_derivation(&result) {
2544            return Some(result);
2545        }
2546    }
2547
2548    // Try cc (congruence closure)
2549    if let Some(result) = try_try_cc_reduce(ctx, &norm_goal) {
2550        if !is_error_derivation(&result) {
2551            return Some(result);
2552        }
2553    }
2554
2555    // Try omega (integer arithmetic - most precise)
2556    if let Some(result) = try_try_omega_reduce(ctx, &norm_goal) {
2557        if !is_error_derivation(&result) {
2558            return Some(result);
2559        }
2560    }
2561
2562    // Try lia (linear arithmetic - fallback)
2563    if let Some(result) = try_try_lia_reduce(ctx, &norm_goal) {
2564        if !is_error_derivation(&result) {
2565            return Some(result);
2566        }
2567    }
2568
2569    // Try registered hints
2570    for hint_name in ctx.get_hints() {
2571        if let Some(hint_type) = ctx.get_global(hint_name) {
2572            if let Some(result) = try_apply_hint(ctx, hint_name, hint_type, &norm_goal) {
2573                return Some(result);
2574            }
2575        }
2576    }
2577
2578    // All tactics failed
2579    Some(make_error_derivation())
2580}
2581
2582/// Verify DAutoSolve proof by re-running tactic search.
2583fn try_dauto_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2584    let norm_goal = normalize(ctx, goal);
2585
2586    // Handle trivial cases: True
2587    if let Term::App(ctor, inner) = &norm_goal {
2588        if let Term::Global(name) = ctor.as_ref() {
2589            if name == "SName" {
2590                if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2591                    if s == "True" {
2592                        return Some(norm_goal.clone());
2593                    }
2594                }
2595            }
2596        }
2597    }
2598
2599    // Try each tactic - if any succeeds, the proof is valid
2600    if let Some(result) = try_try_simp_reduce(ctx, &norm_goal) {
2601        if !is_error_derivation(&result) {
2602            return Some(norm_goal.clone());
2603        }
2604    }
2605    if let Some(result) = try_try_ring_reduce(ctx, &norm_goal) {
2606        if !is_error_derivation(&result) {
2607            return Some(norm_goal.clone());
2608        }
2609    }
2610    if let Some(result) = try_try_cc_reduce(ctx, &norm_goal) {
2611        if !is_error_derivation(&result) {
2612            return Some(norm_goal.clone());
2613        }
2614    }
2615    if let Some(result) = try_try_omega_reduce(ctx, &norm_goal) {
2616        if !is_error_derivation(&result) {
2617            return Some(norm_goal.clone());
2618        }
2619    }
2620    if let Some(result) = try_try_lia_reduce(ctx, &norm_goal) {
2621        if !is_error_derivation(&result) {
2622            return Some(norm_goal.clone());
2623        }
2624    }
2625
2626    // Try registered hints
2627    for hint_name in ctx.get_hints() {
2628        if let Some(hint_type) = ctx.get_global(hint_name) {
2629            if try_apply_hint(ctx, hint_name, hint_type, &norm_goal).is_some() {
2630                return Some(norm_goal);
2631            }
2632        }
2633    }
2634
2635    Some(make_sname_error())
2636}
2637
2638// =============================================================================
2639// GENERIC INDUCTION HELPERS (induction_num_cases, induction_base_goal, etc.)
2640// =============================================================================
2641
2642/// Returns the number of constructors for an inductive type.
2643///
2644/// induction_num_cases (SName "Nat") → Succ (Succ Zero) = 2
2645/// induction_num_cases (SName "Bool") → Succ (Succ Zero) = 2
2646/// induction_num_cases (SApp (SName "List") A) → Succ (Succ Zero) = 2
2647fn try_induction_num_cases_reduce(ctx: &Context, ind_type: &Term) -> Option<Term> {
2648    // Extract inductive name from Syntax
2649    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2650        Some(name) => name,
2651        None => {
2652            // Not a valid inductive type syntax
2653            return Some(Term::Global("Zero".to_string()));
2654        }
2655    };
2656
2657    // Look up constructors
2658    let constructors = ctx.get_constructors(&ind_name);
2659    let num_ctors = constructors.len();
2660
2661    // Build Nat representation: Succ (Succ (... Zero))
2662    let mut result = Term::Global("Zero".to_string());
2663    for _ in 0..num_ctors {
2664        result = Term::App(
2665            Box::new(Term::Global("Succ".to_string())),
2666            Box::new(result),
2667        );
2668    }
2669
2670    Some(result)
2671}
2672
2673/// Returns the base case goal for induction (first constructor).
2674///
2675/// Given ind_type and motive (SLam T body), returns motive[ctor0/var].
2676fn try_induction_base_goal_reduce(
2677    ctx: &Context,
2678    ind_type: &Term,
2679    motive: &Term,
2680) -> Option<Term> {
2681    // Extract inductive name
2682    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2683        Some(name) => name,
2684        None => return Some(make_sname_error()),
2685    };
2686
2687    // Get constructors
2688    let constructors = ctx.get_constructors(&ind_name);
2689    if constructors.is_empty() {
2690        return Some(make_sname_error());
2691    }
2692
2693    // Extract motive body
2694    let motive_body = match extract_slam_body(motive) {
2695        Some(body) => body,
2696        None => return Some(make_sname_error()),
2697    };
2698
2699    // Build goal for first constructor (base case)
2700    let (ctor_name, _) = constructors[0];
2701    build_case_expected(ctx, ctor_name, &constructors, &motive_body, ind_type)
2702}
2703
2704/// Returns the goal for a specific constructor index.
2705///
2706/// Given ind_type, motive, and constructor index (as Nat), returns the case goal.
2707fn try_induction_step_goal_reduce(
2708    ctx: &Context,
2709    ind_type: &Term,
2710    motive: &Term,
2711    ctor_idx: &Term,
2712) -> Option<Term> {
2713    // Extract inductive name
2714    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2715        Some(name) => name,
2716        None => return Some(make_sname_error()),
2717    };
2718
2719    // Get constructors
2720    let constructors = ctx.get_constructors(&ind_name);
2721    if constructors.is_empty() {
2722        return Some(make_sname_error());
2723    }
2724
2725    // Convert Nat to index
2726    let idx = nat_to_usize(ctor_idx)?;
2727    if idx >= constructors.len() {
2728        return Some(make_sname_error());
2729    }
2730
2731    // Extract motive body
2732    let motive_body = match extract_slam_body(motive) {
2733        Some(body) => body,
2734        None => return Some(make_sname_error()),
2735    };
2736
2737    // Build goal for the specified constructor
2738    let (ctor_name, _) = constructors[idx];
2739    build_case_expected(ctx, ctor_name, &constructors, &motive_body, ind_type)
2740}
2741
2742/// Convert a Nat term to usize.
2743///
2744/// Zero → 0
2745/// Succ Zero → 1
2746/// Succ (Succ Zero) → 2
2747fn nat_to_usize(term: &Term) -> Option<usize> {
2748    match term {
2749        Term::Global(name) if name == "Zero" => Some(0),
2750        Term::App(succ, inner) => {
2751            if let Term::Global(name) = succ.as_ref() {
2752                if name == "Succ" {
2753                    return nat_to_usize(inner).map(|n| n + 1);
2754                }
2755            }
2756            None
2757        }
2758        _ => None,
2759    }
2760}
2761
2762/// Generic induction tactic.
2763///
2764/// try_induction ind_type motive cases → DElim ind_type motive cases
2765///
2766/// Delegates to existing DElim infrastructure after basic validation.
2767fn try_try_induction_reduce(
2768    ctx: &Context,
2769    ind_type: &Term,
2770    motive: &Term,
2771    cases: &Term,
2772) -> Option<Term> {
2773    // Extract inductive name to validate
2774    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2775        Some(name) => name,
2776        None => return Some(make_error_derivation()),
2777    };
2778
2779    // Get constructors to validate count
2780    let constructors = ctx.get_constructors(&ind_name);
2781    if constructors.is_empty() {
2782        return Some(make_error_derivation());
2783    }
2784
2785    // Extract case proofs to validate count
2786    let case_proofs = match extract_case_proofs(cases) {
2787        Some(proofs) => proofs,
2788        None => return Some(make_error_derivation()),
2789    };
2790
2791    // Verify case count matches constructor count
2792    if case_proofs.len() != constructors.len() {
2793        return Some(make_error_derivation());
2794    }
2795
2796    // Build DElim term (delegates verification to existing infrastructure)
2797    Some(Term::App(
2798        Box::new(Term::App(
2799            Box::new(Term::App(
2800                Box::new(Term::Global("DElim".to_string())),
2801                Box::new(ind_type.clone()),
2802            )),
2803            Box::new(motive.clone()),
2804        )),
2805        Box::new(cases.clone()),
2806    ))
2807}
2808
2809// -------------------------------------------------------------------------
2810// Deep Induction
2811// -------------------------------------------------------------------------
2812
2813/// DInduction reduction with verification.
2814///
2815/// DInduction motive base step → Forall Nat motive (if verified)
2816///
2817/// Verification:
2818/// 1. Extract motive body from SLam Nat body
2819/// 2. Check that concludes(base) = motive[Zero/0]
2820/// 3. Check that concludes(step) = ∀k:Nat. P(k) → P(Succ k)
2821/// 4. If all checks pass, return Forall Nat motive
2822/// 5. Otherwise, return Error
2823fn try_dinduction_reduce(
2824    ctx: &Context,
2825    motive: &Term,
2826    base: &Term,
2827    step: &Term,
2828) -> Option<Term> {
2829    // Normalize all inputs
2830    let norm_motive = normalize(ctx, motive);
2831    let norm_base = normalize(ctx, base);
2832    let norm_step = normalize(ctx, step);
2833
2834    // 1. Extract motive body (should be SLam (SName "Nat") body)
2835    let motive_body = match extract_slam_body(&norm_motive) {
2836        Some(body) => body,
2837        None => return Some(make_sname_error()),
2838    };
2839
2840    // 2. Compute expected base: motive body with Zero substituted for SVar 0
2841    let zero = make_sname("Zero");
2842    let expected_base = match try_syn_subst_reduce(ctx, &zero, 0, &motive_body) {
2843        Some(b) => b,
2844        None => return Some(make_sname_error()),
2845    };
2846
2847    // 3. Get actual base conclusion
2848    let base_conc = match try_concludes_reduce(ctx, &norm_base) {
2849        Some(c) => c,
2850        None => return Some(make_sname_error()),
2851    };
2852
2853    // 4. Verify base matches expected
2854    if !syntax_equal(&base_conc, &expected_base) {
2855        return Some(make_sname_error());
2856    }
2857
2858    // 5. Build expected step formula: ∀k:Nat. P(k) → P(Succ k)
2859    let expected_step = match build_induction_step_formula(ctx, &motive_body) {
2860        Some(s) => s,
2861        None => return Some(make_sname_error()),
2862    };
2863
2864    // 6. Get actual step conclusion
2865    let step_conc = match try_concludes_reduce(ctx, &norm_step) {
2866        Some(c) => c,
2867        None => return Some(make_sname_error()),
2868    };
2869
2870    // 7. Verify step matches expected
2871    if !syntax_equal(&step_conc, &expected_step) {
2872        return Some(make_sname_error());
2873    }
2874
2875    // 8. Return conclusion: Forall Nat motive
2876    Some(make_forall_nat_syntax(&norm_motive))
2877}
2878
2879/// Build step formula: ∀k:Nat. P(k) → P(Succ k)
2880///
2881/// Given motive body P (which uses SVar 0 for k), builds:
2882/// Forall (SName "Nat") (SLam (SName "Nat") (Implies P P[Succ(SVar 0)/SVar 0]))
2883fn build_induction_step_formula(ctx: &Context, motive_body: &Term) -> Option<Term> {
2884    // P(k) = motive_body (uses SVar 0 for k)
2885    let p_k = motive_body.clone();
2886
2887    // P(Succ k) = motive_body with (SApp (SName "Succ") (SVar 0)) substituted
2888    let succ_var = Term::App(
2889        Box::new(Term::App(
2890            Box::new(Term::Global("SApp".to_string())),
2891            Box::new(make_sname("Succ")),
2892        )),
2893        Box::new(Term::App(
2894            Box::new(Term::Global("SVar".to_string())),
2895            Box::new(Term::Lit(Literal::Int(0))),
2896        )),
2897    );
2898    let p_succ_k = try_syn_subst_reduce(ctx, &succ_var, 0, motive_body)?;
2899
2900    // Implies P(k) P(Succ k)
2901    let implies_body = make_implies_syntax(&p_k, &p_succ_k);
2902
2903    // SLam (SName "Nat") implies_body
2904    let slam = Term::App(
2905        Box::new(Term::App(
2906            Box::new(Term::Global("SLam".to_string())),
2907            Box::new(make_sname("Nat")),
2908        )),
2909        Box::new(implies_body),
2910    );
2911
2912    // Forall (SName "Nat") slam
2913    Some(make_forall_syntax_with_type(&make_sname("Nat"), &slam))
2914}
2915
2916/// Build SApp (SApp (SName "Implies") a) b
2917fn make_implies_syntax(a: &Term, b: &Term) -> Term {
2918    // SApp (SName "Implies") a
2919    let app1 = Term::App(
2920        Box::new(Term::App(
2921            Box::new(Term::Global("SApp".to_string())),
2922            Box::new(make_sname("Implies")),
2923        )),
2924        Box::new(a.clone()),
2925    );
2926
2927    // SApp (SApp (SName "Implies") a) b
2928    Term::App(
2929        Box::new(Term::App(
2930            Box::new(Term::Global("SApp".to_string())),
2931            Box::new(app1),
2932        )),
2933        Box::new(b.clone()),
2934    )
2935}
2936
2937/// Build SApp (SApp (SName "Forall") (SName "Nat")) motive
2938fn make_forall_nat_syntax(motive: &Term) -> Term {
2939    make_forall_syntax_with_type(&make_sname("Nat"), motive)
2940}
2941
2942/// Build SApp (SApp (SName "Forall") type_s) body
2943fn make_forall_syntax_with_type(type_s: &Term, body: &Term) -> Term {
2944    // SApp (SName "Forall") type_s
2945    let app1 = Term::App(
2946        Box::new(Term::App(
2947            Box::new(Term::Global("SApp".to_string())),
2948            Box::new(make_sname("Forall")),
2949        )),
2950        Box::new(type_s.clone()),
2951    );
2952
2953    // SApp (SApp (SName "Forall") type_s) body
2954    Term::App(
2955        Box::new(Term::App(
2956            Box::new(Term::Global("SApp".to_string())),
2957            Box::new(app1),
2958        )),
2959        Box::new(body.clone()),
2960    )
2961}
2962
2963// -------------------------------------------------------------------------
2964// Solver (Computational Reflection)
2965// -------------------------------------------------------------------------
2966
2967/// DCompute reduction with verification.
2968///
2969/// DCompute goal → goal (if verified by computation)
2970///
2971/// Verification:
2972/// 1. Check that goal is (Eq T A B) as Syntax
2973/// 2. Evaluate A and B using syn_eval with bounded fuel
2974/// 3. If eval(A) == eval(B), return goal
2975/// 4. Otherwise, return Error
2976fn try_dcompute_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2977    let norm_goal = normalize(ctx, goal);
2978
2979    // Extract T, A, B from Eq T A B (as Syntax)
2980    // Pattern: SApp (SApp (SApp (SName "Eq") T) A) B
2981    let parts = extract_eq_syntax_parts(&norm_goal);
2982    if parts.is_none() {
2983        // Goal is not an equality - return Error
2984        return Some(make_sname_error());
2985    }
2986    let (_, a, b) = parts.unwrap();
2987
2988    // Evaluate A and B with generous fuel
2989    let fuel = 1000;
2990    let a_eval = match try_syn_eval_reduce(ctx, fuel, &a) {
2991        Some(e) => e,
2992        None => return Some(make_sname_error()),
2993    };
2994    let b_eval = match try_syn_eval_reduce(ctx, fuel, &b) {
2995        Some(e) => e,
2996        None => return Some(make_sname_error()),
2997    };
2998
2999    // Compare normalized results
3000    if syntax_equal(&a_eval, &b_eval) {
3001        Some(norm_goal)
3002    } else {
3003        Some(make_sname_error())
3004    }
3005}
3006
3007/// Extract T, A, B from Eq T A B (as Syntax)
3008///
3009/// Pattern: SApp (SApp (SApp (SName "Eq") T) A) B
3010fn extract_eq_syntax_parts(term: &Term) -> Option<(Term, Term, Term)> {
3011    // term = SApp X B where X = SApp Y A where Y = SApp (SName "Eq") T
3012    // Structure: App(App(SApp, App(App(SApp, App(App(SApp, App(SName, "Eq")), T)), A)), B)
3013    if let Term::App(partial2, b) = term {
3014        if let Term::App(sapp2, inner2) = partial2.as_ref() {
3015            if let Term::Global(sapp2_name) = sapp2.as_ref() {
3016                if sapp2_name != "SApp" {
3017                    return None;
3018                }
3019            } else {
3020                return None;
3021            }
3022
3023            if let Term::App(partial1, a) = inner2.as_ref() {
3024                if let Term::App(sapp1, inner1) = partial1.as_ref() {
3025                    if let Term::Global(sapp1_name) = sapp1.as_ref() {
3026                        if sapp1_name != "SApp" {
3027                            return None;
3028                        }
3029                    } else {
3030                        return None;
3031                    }
3032
3033                    if let Term::App(eq_t, t) = inner1.as_ref() {
3034                        if let Term::App(sapp0, eq_sname) = eq_t.as_ref() {
3035                            if let Term::Global(sapp0_name) = sapp0.as_ref() {
3036                                if sapp0_name != "SApp" {
3037                                    return None;
3038                                }
3039                            } else {
3040                                return None;
3041                            }
3042
3043                            // Check if eq_sname is SName "Eq"
3044                            if let Term::App(sname_ctor, eq_str) = eq_sname.as_ref() {
3045                                if let Term::Global(ctor) = sname_ctor.as_ref() {
3046                                    if ctor == "SName" {
3047                                        if let Term::Lit(Literal::Text(s)) = eq_str.as_ref() {
3048                                            if s == "Eq" {
3049                                                return Some((
3050                                                    t.as_ref().clone(),
3051                                                    a.as_ref().clone(),
3052                                                    b.as_ref().clone(),
3053                                                ));
3054                                            }
3055                                        }
3056                                    }
3057                                }
3058                            }
3059                        }
3060                    }
3061                }
3062            }
3063        }
3064    }
3065    None
3066}
3067
3068// -------------------------------------------------------------------------
3069// Tactic Combinators
3070// -------------------------------------------------------------------------
3071
3072/// Reduce tact_orelse t1 t2 goal
3073///
3074/// - Apply t1 to goal
3075/// - If concludes returns Error, apply t2 to goal
3076/// - Otherwise return t1's result
3077fn try_tact_orelse_reduce(
3078    ctx: &Context,
3079    t1: &Term,
3080    t2: &Term,
3081    goal: &Term,
3082) -> Option<Term> {
3083    let norm_goal = normalize(ctx, goal);
3084
3085    // Apply t1 to goal
3086    let d1_app = Term::App(Box::new(t1.clone()), Box::new(norm_goal.clone()));
3087    let d1 = normalize(ctx, &d1_app);
3088
3089    // Check if t1 succeeded by looking at concludes
3090    if let Some(conc1) = try_concludes_reduce(ctx, &d1) {
3091        if is_error_syntax(&conc1) {
3092            // t1 failed, try t2
3093            let d2_app = Term::App(Box::new(t2.clone()), Box::new(norm_goal));
3094            return Some(normalize(ctx, &d2_app));
3095        } else {
3096            // t1 succeeded
3097            return Some(d1);
3098        }
3099    }
3100
3101    // Couldn't evaluate concludes - return error
3102    Some(make_error_derivation())
3103}
3104
3105/// Check if a Syntax term is SName "Error"
3106fn is_error_syntax(term: &Term) -> bool {
3107    // Pattern: SName "Error" = App(Global("SName"), Lit(Text("Error")))
3108    if let Term::App(ctor, arg) = term {
3109        if let Term::Global(name) = ctor.as_ref() {
3110            if name == "SName" {
3111                if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
3112                    return s == "Error";
3113                }
3114            }
3115        }
3116    }
3117    false
3118}
3119
3120/// Reduce tact_try t goal
3121///
3122/// - Apply t to goal
3123/// - If concludes returns Error, return identity (DAxiom goal)
3124/// - Otherwise return t's result
3125fn try_tact_try_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3126    let norm_goal = normalize(ctx, goal);
3127
3128    // Apply t to goal
3129    let d_app = Term::App(Box::new(t.clone()), Box::new(norm_goal.clone()));
3130    let d = normalize(ctx, &d_app);
3131
3132    // Check if t succeeded by looking at concludes
3133    if let Some(conc) = try_concludes_reduce(ctx, &d) {
3134        if is_error_syntax(&conc) {
3135            // t failed, return identity (DAxiom goal)
3136            return Some(make_daxiom(&norm_goal));
3137        } else {
3138            // t succeeded
3139            return Some(d);
3140        }
3141    }
3142
3143    // Couldn't evaluate concludes - return identity
3144    Some(make_daxiom(&norm_goal))
3145}
3146
3147/// Reduce tact_repeat t goal
3148///
3149/// Apply t repeatedly until it fails or makes no progress.
3150/// Returns the accumulated derivation or identity if first application fails.
3151fn try_tact_repeat_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3152    const MAX_ITERATIONS: usize = 100;
3153
3154    let norm_goal = normalize(ctx, goal);
3155    let mut current_goal = norm_goal.clone();
3156    let mut last_successful_deriv: Option<Term> = None;
3157
3158    for _ in 0..MAX_ITERATIONS {
3159        // Apply t to current goal
3160        let d_app = Term::App(Box::new(t.clone()), Box::new(current_goal.clone()));
3161        let d = normalize(ctx, &d_app);
3162
3163        // Check result
3164        if let Some(conc) = try_concludes_reduce(ctx, &d) {
3165            if is_error_syntax(&conc) {
3166                // Tactic failed - stop and return what we have
3167                break;
3168            }
3169
3170            // Check for no-progress (fixed point)
3171            if syntax_equal(&conc, &current_goal) {
3172                // No progress made - stop
3173                break;
3174            }
3175
3176            // Progress made - continue
3177            current_goal = conc;
3178            last_successful_deriv = Some(d);
3179        } else {
3180            // Couldn't evaluate concludes - stop
3181            break;
3182        }
3183    }
3184
3185    // Return final derivation or identity
3186    last_successful_deriv.or_else(|| Some(make_daxiom(&norm_goal)))
3187}
3188
3189/// Reduce tact_then t1 t2 goal
3190///
3191/// - Apply t1 to goal
3192/// - If t1 fails, return Error
3193/// - Otherwise apply t2 to the result of t1
3194fn try_tact_then_reduce(
3195    ctx: &Context,
3196    t1: &Term,
3197    t2: &Term,
3198    goal: &Term,
3199) -> Option<Term> {
3200    let norm_goal = normalize(ctx, goal);
3201
3202    // Apply t1 to goal
3203    let d1_app = Term::App(Box::new(t1.clone()), Box::new(norm_goal.clone()));
3204    let d1 = normalize(ctx, &d1_app);
3205
3206    // Check if t1 succeeded
3207    if let Some(conc1) = try_concludes_reduce(ctx, &d1) {
3208        if is_error_syntax(&conc1) {
3209            // t1 failed
3210            return Some(make_error_derivation());
3211        }
3212
3213        // t1 succeeded - apply t2 to the new goal (conc1)
3214        let d2_app = Term::App(Box::new(t2.clone()), Box::new(conc1));
3215        let d2 = normalize(ctx, &d2_app);
3216
3217        // The result is d2 (which may succeed or fail)
3218        return Some(d2);
3219    }
3220
3221    // Couldn't evaluate concludes - return error
3222    Some(make_error_derivation())
3223}
3224
3225/// Reduce tact_first tactics goal
3226///
3227/// Try each tactic in the list until one succeeds.
3228/// Returns Error if all fail or list is empty.
3229fn try_tact_first_reduce(ctx: &Context, tactics: &Term, goal: &Term) -> Option<Term> {
3230    let norm_goal = normalize(ctx, goal);
3231
3232    // Extract tactics from TList
3233    let tactic_vec = extract_tlist(tactics)?;
3234
3235    for tactic in tactic_vec {
3236        // Apply this tactic to goal
3237        let d_app = Term::App(Box::new(tactic), Box::new(norm_goal.clone()));
3238        let d = normalize(ctx, &d_app);
3239
3240        // Check if it succeeded
3241        if let Some(conc) = try_concludes_reduce(ctx, &d) {
3242            if !is_error_syntax(&conc) {
3243                // Success!
3244                return Some(d);
3245            }
3246            // Failed - try next
3247        }
3248    }
3249
3250    // All failed
3251    Some(make_error_derivation())
3252}
3253
3254/// Extract elements from a TList term
3255///
3256/// TList is polymorphic: TNil A and TCons A h t
3257/// So the structure is:
3258/// - TNil A = App(Global("TNil"), type)
3259/// - TCons A h t = App(App(App(Global("TCons"), type), head), tail)
3260fn extract_tlist(term: &Term) -> Option<Vec<Term>> {
3261    let mut result = Vec::new();
3262    let mut current = term.clone();
3263
3264    loop {
3265        match &current {
3266            // TNil A = App(Global("TNil"), type)
3267            Term::App(tnil, _type) => {
3268                if let Term::Global(name) = tnil.as_ref() {
3269                    if name == "TNil" {
3270                        // Empty list
3271                        break;
3272                    }
3273                }
3274                // Try TCons A h t = App(App(App(Global("TCons"), type), head), tail)
3275                if let Term::App(partial2, tail) = &current {
3276                    if let Term::App(partial1, head) = partial2.as_ref() {
3277                        if let Term::App(tcons, _type) = partial1.as_ref() {
3278                            if let Term::Global(name) = tcons.as_ref() {
3279                                if name == "TCons" {
3280                                    result.push(head.as_ref().clone());
3281                                    current = tail.as_ref().clone();
3282                                    continue;
3283                                }
3284                            }
3285                        }
3286                    }
3287                }
3288                // Not a valid TList structure
3289                return None;
3290            }
3291            // Bare Global("TNil") without type argument - also valid
3292            Term::Global(name) if name == "TNil" => {
3293                break;
3294            }
3295            _ => {
3296                // Not a valid TList
3297                return None;
3298            }
3299        }
3300    }
3301
3302    Some(result)
3303}
3304
3305/// Reduce tact_solve t goal
3306///
3307/// - Apply t to goal
3308/// - If t fails (Error), return Error
3309/// - If t succeeds, return its result
3310fn try_tact_solve_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3311    let norm_goal = normalize(ctx, goal);
3312
3313    // Apply t to goal
3314    let d_app = Term::App(Box::new(t.clone()), Box::new(norm_goal.clone()));
3315    let d = normalize(ctx, &d_app);
3316
3317    // Check if t succeeded
3318    if let Some(conc) = try_concludes_reduce(ctx, &d) {
3319        if is_error_syntax(&conc) {
3320            // t failed
3321            return Some(make_error_derivation());
3322        }
3323        // t succeeded - return its derivation
3324        return Some(d);
3325    }
3326
3327    // Couldn't evaluate concludes - return error
3328    Some(make_error_derivation())
3329}
3330
3331// -------------------------------------------------------------------------
3332// Congruence Closure
3333// -------------------------------------------------------------------------
3334
3335/// Validate DCong proof by congruence.
3336///
3337/// DCong context eq_proof where:
3338/// - context is SLam param_type body
3339/// - eq_proof proves Eq T a b
3340/// Returns: Eq param_type (body[0:=a]) (body[0:=b])
3341fn try_dcong_conclude(ctx: &Context, context: &Term, eq_proof: &Term) -> Option<Term> {
3342    // Get the conclusion of the equality proof
3343    let eq_conc = try_concludes_reduce(ctx, eq_proof)?;
3344
3345    // Extract T, a, b from Eq T a b
3346    let parts = extract_eq_syntax_parts(&eq_conc);
3347    if parts.is_none() {
3348        // Not an equality proof
3349        return Some(make_sname_error());
3350    }
3351    let (_type_term, lhs, rhs) = parts.unwrap();
3352
3353    // Normalize context and check it's a lambda
3354    let norm_context = normalize(ctx, context);
3355
3356    // Extract (param_type, body) from SLam param_type body
3357    let slam_parts = extract_slam_parts(&norm_context);
3358    if slam_parts.is_none() {
3359        // Not a lambda context
3360        return Some(make_sname_error());
3361    }
3362    let (param_type, body) = slam_parts.unwrap();
3363
3364    // Substitute lhs and rhs into body at index 0
3365    let fa = try_syn_subst_reduce(ctx, &lhs, 0, &body)?;
3366    let fb = try_syn_subst_reduce(ctx, &rhs, 0, &body)?;
3367
3368    // Build result: Eq param_type fa fb
3369    Some(make_eq_syntax_three(&param_type, &fa, &fb))
3370}
3371
3372/// Extract (param_type, body) from SLam param_type body
3373///
3374/// Pattern: App(App(Global("SLam"), param_type), body)
3375fn extract_slam_parts(term: &Term) -> Option<(Term, Term)> {
3376    if let Term::App(inner, body) = term {
3377        if let Term::App(slam_ctor, param_type) = inner.as_ref() {
3378            if let Term::Global(name) = slam_ctor.as_ref() {
3379                if name == "SLam" {
3380                    return Some((param_type.as_ref().clone(), body.as_ref().clone()));
3381                }
3382            }
3383        }
3384    }
3385    None
3386}
3387
3388/// Build SApp (SApp (SApp (SName "Eq") type_s) a) b
3389///
3390/// Constructs the Syntax representation of (Eq type_s a b)
3391fn make_eq_syntax_three(type_s: &Term, a: &Term, b: &Term) -> Term {
3392    let eq_name = Term::App(
3393        Box::new(Term::Global("SName".to_string())),
3394        Box::new(Term::Lit(Literal::Text("Eq".to_string()))),
3395    );
3396
3397    // SApp (SName "Eq") type_s
3398    let app1 = Term::App(
3399        Box::new(Term::App(
3400            Box::new(Term::Global("SApp".to_string())),
3401            Box::new(eq_name),
3402        )),
3403        Box::new(type_s.clone()),
3404    );
3405
3406    // SApp (SApp (SName "Eq") type_s) a
3407    let app2 = Term::App(
3408        Box::new(Term::App(
3409            Box::new(Term::Global("SApp".to_string())),
3410            Box::new(app1),
3411        )),
3412        Box::new(a.clone()),
3413    );
3414
3415    // SApp (SApp (SApp (SName "Eq") type_s) a) b
3416    Term::App(
3417        Box::new(Term::App(
3418            Box::new(Term::Global("SApp".to_string())),
3419            Box::new(app2),
3420        )),
3421        Box::new(b.clone()),
3422    )
3423}
3424
3425// -------------------------------------------------------------------------
3426// Generic Elimination
3427// -------------------------------------------------------------------------
3428
3429/// DElim reduction with verification.
3430///
3431/// DElim ind_type motive cases → Forall ind_type motive (if verified)
3432///
3433/// Verification:
3434/// 1. Extract inductive name from ind_type Syntax
3435/// 2. Look up constructors for that inductive
3436/// 3. Extract case proofs from DCase chain
3437/// 4. Verify case count matches constructor count
3438/// 5. For each constructor, verify case conclusion matches expected
3439/// 6. Return Forall ind_type motive
3440fn try_delim_conclude(
3441    ctx: &Context,
3442    ind_type: &Term,
3443    motive: &Term,
3444    cases: &Term,
3445) -> Option<Term> {
3446    // Normalize inputs
3447    let norm_ind_type = normalize(ctx, ind_type);
3448    let norm_motive = normalize(ctx, motive);
3449    let norm_cases = normalize(ctx, cases);
3450
3451    // 1. Extract inductive name from Syntax
3452    let ind_name = match extract_inductive_name_from_syntax(&norm_ind_type) {
3453        Some(name) => name,
3454        None => return Some(make_sname_error()),
3455    };
3456
3457    // 2. Look up constructors for this inductive
3458    let constructors = ctx.get_constructors(&ind_name);
3459    if constructors.is_empty() {
3460        // Unknown inductive type
3461        return Some(make_sname_error());
3462    }
3463
3464    // 3. Extract case proofs from DCase chain
3465    let case_proofs = match extract_case_proofs(&norm_cases) {
3466        Some(proofs) => proofs,
3467        None => return Some(make_sname_error()),
3468    };
3469
3470    // 4. Verify case count matches constructor count
3471    if case_proofs.len() != constructors.len() {
3472        return Some(make_sname_error());
3473    }
3474
3475    // 5. Extract motive body (should be SLam param_type body)
3476    let motive_body = match extract_slam_body(&norm_motive) {
3477        Some(body) => body,
3478        None => return Some(make_sname_error()),
3479    };
3480
3481    // 6. For each constructor, verify case conclusion matches expected
3482    for (i, (ctor_name, _ctor_type)) in constructors.iter().enumerate() {
3483        let case_proof = &case_proofs[i];
3484
3485        // Get actual conclusion of this case proof
3486        let case_conc = match try_concludes_reduce(ctx, case_proof) {
3487            Some(c) => c,
3488            None => return Some(make_sname_error()),
3489        };
3490
3491        // Build expected conclusion based on constructor
3492        // For base case (0-ary constructor): motive[ctor/var]
3493        // For step case (recursive constructor): requires IH pattern
3494        let expected = match build_case_expected(ctx, ctor_name, &constructors, &motive_body, &norm_ind_type) {
3495            Some(e) => e,
3496            None => return Some(make_sname_error()),
3497        };
3498
3499        // Verify conclusion matches expected
3500        if !syntax_equal(&case_conc, &expected) {
3501            return Some(make_sname_error());
3502        }
3503    }
3504
3505    // 7. Return conclusion: Forall ind_type motive
3506    Some(make_forall_syntax_generic(&norm_ind_type, &norm_motive))
3507}
3508
3509/// Extract inductive name from Syntax term.
3510///
3511/// Handles:
3512/// - SName "Nat" → "Nat"
3513/// - SApp (SName "List") A → "List"
3514fn extract_inductive_name_from_syntax(term: &Term) -> Option<String> {
3515    // Case 1: SName "X"
3516    if let Term::App(sname, text) = term {
3517        if let Term::Global(ctor) = sname.as_ref() {
3518            if ctor == "SName" {
3519                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
3520                    return Some(name.clone());
3521                }
3522            }
3523        }
3524    }
3525
3526    // Case 2: SApp (SName "X") args → extract "X" from the function position
3527    if let Term::App(inner, _arg) = term {
3528        if let Term::App(sapp, func) = inner.as_ref() {
3529            if let Term::Global(ctor) = sapp.as_ref() {
3530                if ctor == "SApp" {
3531                    // Recursively extract from the function
3532                    return extract_inductive_name_from_syntax(func);
3533                }
3534            }
3535        }
3536    }
3537
3538    None
3539}
3540
3541/// Extract case proofs from DCase chain.
3542///
3543/// DCase p1 (DCase p2 DCaseEnd) → [p1, p2]
3544fn extract_case_proofs(term: &Term) -> Option<Vec<Term>> {
3545    let mut proofs = Vec::new();
3546    let mut current = term;
3547
3548    loop {
3549        // DCaseEnd - end of list
3550        if let Term::Global(name) = current {
3551            if name == "DCaseEnd" {
3552                return Some(proofs);
3553            }
3554        }
3555
3556        // DCase head tail - Pattern: App(App(DCase, head), tail)
3557        if let Term::App(inner, tail) = current {
3558            if let Term::App(dcase, head) = inner.as_ref() {
3559                if let Term::Global(name) = dcase.as_ref() {
3560                    if name == "DCase" {
3561                        proofs.push(head.as_ref().clone());
3562                        current = tail.as_ref();
3563                        continue;
3564                    }
3565                }
3566            }
3567        }
3568
3569        // Unrecognized structure
3570        return None;
3571    }
3572}
3573
3574/// Build expected case conclusion for a constructor.
3575///
3576/// For base case constructors (no recursive args): motive[ctor/var]
3577/// For recursive constructors: ∀args. IH → motive[ctor args/var]
3578fn build_case_expected(
3579    ctx: &Context,
3580    ctor_name: &str,
3581    _constructors: &[(&str, &Term)],
3582    motive_body: &Term,
3583    ind_type: &Term,
3584) -> Option<Term> {
3585    // Extract inductive name to determine constructor patterns
3586    let ind_name = extract_inductive_name_from_syntax(ind_type)?;
3587
3588    // Special case for Nat - we know its structure
3589    if ind_name == "Nat" {
3590        if ctor_name == "Zero" {
3591            // Base case: motive[Zero/var]
3592            let zero = make_sname("Zero");
3593            return try_syn_subst_reduce(ctx, &zero, 0, motive_body);
3594        } else if ctor_name == "Succ" {
3595            // Step case: ∀k:Nat. P(k) → P(Succ k)
3596            // Use the same logic as DInduction
3597            return build_induction_step_formula(ctx, motive_body);
3598        }
3599    }
3600
3601    // For other inductives, use heuristic based on constructor type
3602    // Build the constructor as Syntax: SName "CtorName"
3603    let ctor_syntax = make_sname(ctor_name);
3604
3605    // For polymorphic types, we need to apply the type argument to the constructor
3606    // e.g., for List A, Nil becomes (SApp (SName "Nil") A)
3607    let ctor_applied = apply_type_args_to_ctor(&ctor_syntax, ind_type);
3608
3609    // Get constructor type from context to determine if it's recursive
3610    if let Some(ctor_ty) = ctx.get_global(ctor_name) {
3611        // Check if constructor type contains the inductive type (recursive)
3612        if is_recursive_constructor(ctx, ctor_ty, &ind_name, ind_type) {
3613            // For recursive constructors, build the IH pattern
3614            return build_recursive_case_formula(ctx, ctor_name, ctor_ty, motive_body, ind_type, &ind_name);
3615        }
3616    }
3617
3618    // Simple base case: substitute ctor into motive body
3619    try_syn_subst_reduce(ctx, &ctor_applied, 0, motive_body)
3620}
3621
3622/// Apply type arguments from ind_type to a constructor.
3623///
3624/// If ind_type = SApp (SName "List") A, and ctor = SName "Nil",
3625/// result = SApp (SName "Nil") A
3626fn apply_type_args_to_ctor(ctor: &Term, ind_type: &Term) -> Term {
3627    // Extract type arguments from ind_type
3628    let args = extract_type_args(ind_type);
3629
3630    if args.is_empty() {
3631        return ctor.clone();
3632    }
3633
3634    // Apply each arg: SApp (... (SApp ctor arg1) ...) argN
3635    args.iter().fold(ctor.clone(), |acc, arg| {
3636        Term::App(
3637            Box::new(Term::App(
3638                Box::new(Term::Global("SApp".to_string())),
3639                Box::new(acc),
3640            )),
3641            Box::new(arg.clone()),
3642        )
3643    })
3644}
3645
3646/// Extract type arguments from polymorphic Syntax.
3647///
3648/// SApp (SApp (SName "Either") A) B → [A, B]
3649/// SApp (SName "List") A → [A]
3650/// SName "Nat" → []
3651fn extract_type_args(term: &Term) -> Vec<Term> {
3652    let mut args = Vec::new();
3653    let mut current = term;
3654
3655    // Traverse SApp chain from outside in
3656    loop {
3657        if let Term::App(inner, arg) = current {
3658            if let Term::App(sapp, func) = inner.as_ref() {
3659                if let Term::Global(ctor) = sapp.as_ref() {
3660                    if ctor == "SApp" {
3661                        args.push(arg.as_ref().clone());
3662                        current = func.as_ref();
3663                        continue;
3664                    }
3665                }
3666            }
3667        }
3668        break;
3669    }
3670
3671    // Reverse because we collected outside-in but want inside-out
3672    args.reverse();
3673    args
3674}
3675
3676/// Build Forall Syntax for generic inductive type.
3677///
3678/// Forall ind_type motive = SApp (SApp (SName "Forall") ind_type) motive
3679fn make_forall_syntax_generic(ind_type: &Term, motive: &Term) -> Term {
3680    // SApp (SName "Forall") ind_type
3681    let forall_type = Term::App(
3682        Box::new(Term::App(
3683            Box::new(Term::Global("SApp".to_string())),
3684            Box::new(make_sname("Forall")),
3685        )),
3686        Box::new(ind_type.clone()),
3687    );
3688
3689    // SApp forall_type motive
3690    Term::App(
3691        Box::new(Term::App(
3692            Box::new(Term::Global("SApp".to_string())),
3693            Box::new(forall_type),
3694        )),
3695        Box::new(motive.clone()),
3696    )
3697}
3698
3699/// Check if a constructor is recursive (has arguments of the inductive type).
3700fn is_recursive_constructor(
3701    _ctx: &Context,
3702    ctor_ty: &Term,
3703    ind_name: &str,
3704    _ind_type: &Term,
3705) -> bool {
3706    // Traverse the constructor type looking for the inductive type in argument positions
3707    // For Cons : Π(A:Type). A -> List A -> List A
3708    // The "List A" argument makes it recursive
3709
3710    fn contains_inductive(term: &Term, ind_name: &str) -> bool {
3711        match term {
3712            Term::Global(name) => name == ind_name,
3713            Term::App(f, a) => {
3714                contains_inductive(f, ind_name) || contains_inductive(a, ind_name)
3715            }
3716            Term::Pi { param_type, body_type, .. } => {
3717                contains_inductive(param_type, ind_name) || contains_inductive(body_type, ind_name)
3718            }
3719            Term::Lambda { param_type, body, .. } => {
3720                contains_inductive(param_type, ind_name) || contains_inductive(body, ind_name)
3721            }
3722            _ => false,
3723        }
3724    }
3725
3726    // Check if any parameter type (not the final result) contains the inductive
3727    fn check_params(term: &Term, ind_name: &str) -> bool {
3728        match term {
3729            Term::Pi { param_type, body_type, .. } => {
3730                // Check if this parameter has the inductive type
3731                if contains_inductive(param_type, ind_name) {
3732                    return true;
3733                }
3734                // Check remaining parameters
3735                check_params(body_type, ind_name)
3736            }
3737            _ => false,
3738        }
3739    }
3740
3741    check_params(ctor_ty, ind_name)
3742}
3743
3744/// Build the case formula for a recursive constructor.
3745///
3746/// For Cons : Π(A:Type). A -> List A -> List A
3747/// with motive P : List A -> Prop
3748/// Expected case: ∀x:A. ∀xs:List A. P(xs) -> P(Cons A x xs)
3749fn build_recursive_case_formula(
3750    ctx: &Context,
3751    ctor_name: &str,
3752    ctor_ty: &Term,
3753    motive_body: &Term,
3754    ind_type: &Term,
3755    ind_name: &str,
3756) -> Option<Term> {
3757    // Extract type args from ind_type for matching
3758    let type_args = extract_type_args(ind_type);
3759
3760    // Collect constructor arguments (skipping type parameters)
3761    let args = collect_ctor_args(ctor_ty, ind_name, &type_args);
3762
3763    if args.is_empty() {
3764        // No non-type arguments, treat as base case
3765        let ctor_applied = apply_type_args_to_ctor(&make_sname(ctor_name), ind_type);
3766        return try_syn_subst_reduce(ctx, &ctor_applied, 0, motive_body);
3767    }
3768
3769    // Build from inside out:
3770    // 1. Build ctor application: Cons A x xs (with de Bruijn indices for args)
3771    // 2. Build P(ctor args): motive_body[ctor args/var]
3772    // 3. For each recursive arg, wrap with IH: P(xs) ->
3773    // 4. For each arg, wrap with forall: ∀xs:List A.
3774
3775    // Build constructor application with de Bruijn indices
3776    let mut ctor_app = apply_type_args_to_ctor(&make_sname(ctor_name), ind_type);
3777    for (i, _) in args.iter().enumerate() {
3778        // Index from end: last arg is index 0, second-to-last is 1, etc.
3779        let idx = (args.len() - 1 - i) as i64;
3780        let var = Term::App(
3781            Box::new(Term::Global("SVar".to_string())),
3782            Box::new(Term::Lit(Literal::Int(idx))),
3783        );
3784        ctor_app = Term::App(
3785            Box::new(Term::App(
3786                Box::new(Term::Global("SApp".to_string())),
3787                Box::new(ctor_app),
3788            )),
3789            Box::new(var),
3790        );
3791    }
3792
3793    // P(ctor args) - substitute ctor_app into motive
3794    let p_ctor = try_syn_subst_reduce(ctx, &ctor_app, 0, motive_body)?;
3795
3796    // Build implications from inside out (for recursive args)
3797    let mut body = p_ctor;
3798    for (i, (arg_ty, is_recursive)) in args.iter().enumerate().rev() {
3799        if *is_recursive {
3800            // Add IH: P(arg) -> body
3801            // arg is at index (args.len() - 1 - i)
3802            let idx = (args.len() - 1 - i) as i64;
3803            let var = Term::App(
3804                Box::new(Term::Global("SVar".to_string())),
3805                Box::new(Term::Lit(Literal::Int(idx))),
3806            );
3807            let p_arg = try_syn_subst_reduce(ctx, &var, 0, motive_body)?;
3808            body = make_implies_syntax(&p_arg, &body);
3809        }
3810        // Skip non-recursive args in the implication chain
3811        let _ = (i, arg_ty); // suppress unused warning
3812    }
3813
3814    // Wrap with foralls from inside out
3815    for (arg_ty, _) in args.iter().rev() {
3816        // SLam arg_ty body
3817        let slam = Term::App(
3818            Box::new(Term::App(
3819                Box::new(Term::Global("SLam".to_string())),
3820                Box::new(arg_ty.clone()),
3821            )),
3822            Box::new(body.clone()),
3823        );
3824        // Forall arg_ty slam
3825        body = make_forall_syntax_with_type(arg_ty, &slam);
3826    }
3827
3828    Some(body)
3829}
3830
3831/// Collect constructor arguments, skipping type parameters.
3832/// Returns (arg_type, is_recursive) pairs.
3833fn collect_ctor_args(ctor_ty: &Term, ind_name: &str, type_args: &[Term]) -> Vec<(Term, bool)> {
3834    let mut args = Vec::new();
3835    let mut current = ctor_ty;
3836    let mut skip_count = type_args.len();
3837
3838    loop {
3839        match current {
3840            Term::Pi { param_type, body_type, .. } => {
3841                if skip_count > 0 {
3842                    // Skip type parameter
3843                    skip_count -= 1;
3844                } else {
3845                    // Regular argument
3846                    let is_recursive = contains_inductive_term(param_type, ind_name);
3847                    // Convert kernel type to Syntax representation
3848                    let arg_ty_syntax = kernel_type_to_syntax(param_type);
3849                    args.push((arg_ty_syntax, is_recursive));
3850                }
3851                current = body_type;
3852            }
3853            _ => break,
3854        }
3855    }
3856
3857    args
3858}
3859
3860/// Check if a kernel Term contains the inductive type.
3861fn contains_inductive_term(term: &Term, ind_name: &str) -> bool {
3862    match term {
3863        Term::Global(name) => name == ind_name,
3864        Term::App(f, a) => {
3865            contains_inductive_term(f, ind_name) || contains_inductive_term(a, ind_name)
3866        }
3867        Term::Pi { param_type, body_type, .. } => {
3868            contains_inductive_term(param_type, ind_name) || contains_inductive_term(body_type, ind_name)
3869        }
3870        Term::Lambda { param_type, body, .. } => {
3871            contains_inductive_term(param_type, ind_name) || contains_inductive_term(body, ind_name)
3872        }
3873        _ => false,
3874    }
3875}
3876
3877/// Convert a kernel Term (type) to its Syntax representation.
3878fn kernel_type_to_syntax(term: &Term) -> Term {
3879    match term {
3880        Term::Global(name) => make_sname(name),
3881        Term::Var(name) => make_sname(name), // Named variable
3882        Term::App(f, a) => {
3883            let f_syn = kernel_type_to_syntax(f);
3884            let a_syn = kernel_type_to_syntax(a);
3885            // SApp f_syn a_syn
3886            Term::App(
3887                Box::new(Term::App(
3888                    Box::new(Term::Global("SApp".to_string())),
3889                    Box::new(f_syn),
3890                )),
3891                Box::new(a_syn),
3892            )
3893        }
3894        Term::Pi { param, param_type, body_type } => {
3895            let pt_syn = kernel_type_to_syntax(param_type);
3896            let bt_syn = kernel_type_to_syntax(body_type);
3897            // SPi pt_syn bt_syn
3898            Term::App(
3899                Box::new(Term::App(
3900                    Box::new(Term::Global("SPi".to_string())),
3901                    Box::new(pt_syn),
3902                )),
3903                Box::new(bt_syn),
3904            )
3905        }
3906        Term::Sort(univ) => {
3907            // SSort univ
3908            Term::App(
3909                Box::new(Term::Global("SSort".to_string())),
3910                Box::new(univ_to_syntax(univ)),
3911            )
3912        }
3913        Term::Lit(lit) => {
3914            // SLit lit
3915            Term::App(
3916                Box::new(Term::Global("SLit".to_string())),
3917                Box::new(Term::Lit(lit.clone())),
3918            )
3919        }
3920        _ => {
3921            // Fallback for complex terms
3922            make_sname("Unknown")
3923        }
3924    }
3925}
3926
3927/// Convert a Universe to Syntax.
3928fn univ_to_syntax(univ: &crate::term::Universe) -> Term {
3929    match univ {
3930        crate::term::Universe::Prop => Term::Global("UProp".to_string()),
3931        crate::term::Universe::Type(n) => Term::App(
3932            Box::new(Term::Global("UType".to_string())),
3933            Box::new(Term::Lit(Literal::Int(*n as i64))),
3934        ),
3935    }
3936}
3937
3938// -------------------------------------------------------------------------
3939// Inversion Tactic
3940// -------------------------------------------------------------------------
3941
3942/// Inversion tactic: analyze hypothesis to derive False if no constructor matches.
3943///
3944/// Given hypothesis H of form `SApp (SName "IndName") args`, check if any constructor
3945/// of IndName can produce those args. If no constructor can match, return DInversion H.
3946fn try_try_inversion_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
3947    // Extract inductive name and arguments from the hypothesis type
3948    let (ind_name, hyp_args) = match extract_applied_inductive_from_syntax(goal) {
3949        Some((name, args)) => (name, args),
3950        None => return Some(make_error_derivation()),
3951    };
3952
3953    // Check if the inductive type actually exists
3954    if !ctx.is_inductive(&ind_name) {
3955        // Unknown inductive type - cannot derive anything
3956        return Some(make_error_derivation());
3957    }
3958
3959    // Get constructors for this inductive
3960    let constructors = ctx.get_constructors(&ind_name);
3961
3962    // Check each constructor to see if it can match
3963    let mut any_possible = false;
3964    for (_ctor_name, ctor_type) in constructors.iter() {
3965        if can_constructor_match_args(ctx, ctor_type, &hyp_args, &ind_name) {
3966            any_possible = true;
3967            break;
3968        }
3969    }
3970
3971    if any_possible {
3972        // Cannot derive False - some constructor could match
3973        return Some(make_error_derivation());
3974    }
3975
3976    // All constructors impossible → build DInversion
3977    Some(Term::App(
3978        Box::new(Term::Global("DInversion".to_string())),
3979        Box::new(goal.clone()),
3980    ))
3981}
3982
3983/// Verify DInversion proof: check that no constructor can match the hypothesis.
3984fn try_dinversion_conclude(ctx: &Context, hyp_type: &Term) -> Option<Term> {
3985    let norm_hyp = normalize(ctx, hyp_type);
3986
3987    let (ind_name, hyp_args) = match extract_applied_inductive_from_syntax(&norm_hyp) {
3988        Some((name, args)) => (name, args),
3989        None => return Some(make_sname_error()),
3990    };
3991
3992    // Check if the inductive type actually exists
3993    if !ctx.is_inductive(&ind_name) {
3994        return Some(make_sname_error());
3995    }
3996
3997    let constructors = ctx.get_constructors(&ind_name);
3998
3999    // Verify ALL constructors are impossible
4000    for (_ctor_name, ctor_type) in constructors.iter() {
4001        if can_constructor_match_args(ctx, ctor_type, &hyp_args, &ind_name) {
4002            return Some(make_sname_error());
4003        }
4004    }
4005
4006    // All impossible → concludes False
4007    Some(make_sname("False"))
4008}
4009
4010/// Extract inductive name and arguments from Syntax.
4011///
4012/// SApp (SApp (SName "Even") x) y → ("Even", [x, y])
4013/// SName "False" → ("False", [])
4014fn extract_applied_inductive_from_syntax(term: &Term) -> Option<(String, Vec<Term>)> {
4015    // Base case: SName "X"
4016    if let Term::App(ctor, text) = term {
4017        if let Term::Global(ctor_name) = ctor.as_ref() {
4018            if ctor_name == "SName" {
4019                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
4020                    return Some((name.clone(), vec![]));
4021                }
4022            }
4023        }
4024    }
4025
4026    // Recursive case: SApp f x
4027    if let Term::App(inner, arg) = term {
4028        if let Term::App(sapp_ctor, func) = inner.as_ref() {
4029            if let Term::Global(ctor_name) = sapp_ctor.as_ref() {
4030                if ctor_name == "SApp" {
4031                    // Recursively extract from the function
4032                    let (name, mut args) = extract_applied_inductive_from_syntax(func)?;
4033                    args.push(arg.as_ref().clone());
4034                    return Some((name, args));
4035                }
4036            }
4037        }
4038    }
4039
4040    None
4041}
4042
4043/// Check if a constructor can possibly match the given arguments.
4044///
4045/// For constructor `even_succ : ∀n. Even n → Even (Succ (Succ n))`:
4046/// - The constructor's result pattern is `Even (Succ (Succ n))`
4047/// - If hyp_args is `[Succ (Succ (Succ Zero))]` (representing 3):
4048///   - Unify `Succ (Succ n)` with `Succ (Succ (Succ Zero))`
4049///   - This succeeds with n = Succ Zero = 1
4050///   - But then we need to check if `Even 1` is constructible (recursive)
4051fn can_constructor_match_args(
4052    ctx: &Context,
4053    ctor_type: &Term,
4054    hyp_args: &[Term],
4055    ind_name: &str,
4056) -> bool {
4057    // Decompose constructor type to get result pattern and bound variable names
4058    let (result, pattern_vars) = decompose_ctor_type_with_vars(ctor_type);
4059
4060    // Extract result's arguments (what the constructor produces)
4061    let result_args = match extract_applied_inductive_from_syntax(&kernel_type_to_syntax(&result)) {
4062        Some((name, args)) if name == *ind_name => args,
4063        _ => return false,
4064    };
4065
4066    // If argument counts don't match, can't unify
4067    if result_args.len() != hyp_args.len() {
4068        return false;
4069    }
4070
4071    // Try syntactic unification of all arguments together (tracking bindings across args)
4072    let mut bindings: std::collections::HashMap<String, Term> = std::collections::HashMap::new();
4073
4074    for (pattern, concrete) in result_args.iter().zip(hyp_args.iter()) {
4075        if !can_unify_syntax_terms_with_bindings(ctx, pattern, concrete, &pattern_vars, &mut bindings) {
4076            return false;
4077        }
4078    }
4079
4080    // If we get here, the constructor could match
4081    // (We don't check recursive hypotheses for simplicity - that would require
4082    // full inversion with backtracking)
4083    true
4084}
4085
4086/// Decompose a constructor type to get the result type and bound variable names.
4087///
4088/// `∀n:Nat. Even n → Even (Succ (Succ n))` → (`Even (Succ (Succ n))`, ["n"])
4089/// `∀A:Type. ∀x:A. Eq A x x` → (`Eq A x x`, ["A", "x"])
4090/// `Bool` → (`Bool`, [])
4091fn decompose_ctor_type_with_vars(ty: &Term) -> (Term, Vec<String>) {
4092    let mut vars = Vec::new();
4093    let mut current = ty;
4094    loop {
4095        match current {
4096            Term::Pi { param, body_type, .. } => {
4097                vars.push(param.clone());
4098                current = body_type;
4099            }
4100            _ => break,
4101        }
4102    }
4103    (current.clone(), vars)
4104}
4105
4106/// Check if two Syntax terms can unify, tracking variable bindings.
4107///
4108/// Pattern variables (names in `pattern_vars`) can bind to any concrete value,
4109/// but must bind consistently (same variable must bind to same value).
4110/// Other SNames must match exactly.
4111/// SApp recurses on function and argument.
4112fn can_unify_syntax_terms_with_bindings(
4113    ctx: &Context,
4114    pattern: &Term,
4115    concrete: &Term,
4116    pattern_vars: &[String],
4117    bindings: &mut std::collections::HashMap<String, Term>,
4118) -> bool {
4119    // SVar can match anything (explicit unification variable)
4120    if let Term::App(ctor, _idx) = pattern {
4121        if let Term::Global(name) = ctor.as_ref() {
4122            if name == "SVar" {
4123                return true;
4124            }
4125        }
4126    }
4127
4128    // SName: check if it's a pattern variable or a constant
4129    if let Term::App(ctor1, text1) = pattern {
4130        if let Term::Global(n1) = ctor1.as_ref() {
4131            if n1 == "SName" {
4132                if let Term::Lit(Literal::Text(var_name)) = text1.as_ref() {
4133                    // Check if this is a pattern variable
4134                    if pattern_vars.contains(var_name) {
4135                        // Pattern variable: check existing binding or create new one
4136                        if let Some(existing) = bindings.get(var_name) {
4137                            // Already bound: concrete must match existing binding
4138                            return syntax_terms_equal(existing, concrete);
4139                        } else {
4140                            // Not yet bound: bind to concrete value
4141                            bindings.insert(var_name.clone(), concrete.clone());
4142                            return true;
4143                        }
4144                    }
4145                }
4146                // Not a pattern variable: must match exactly
4147                if let Term::App(ctor2, text2) = concrete {
4148                    if let Term::Global(n2) = ctor2.as_ref() {
4149                        if n2 == "SName" {
4150                            return text1 == text2;
4151                        }
4152                    }
4153                }
4154                return false;
4155            }
4156        }
4157    }
4158
4159    // SApp: recurse on both function and argument
4160    if let (Term::App(inner1, arg1), Term::App(inner2, arg2)) = (pattern, concrete) {
4161        if let (Term::App(sapp1, func1), Term::App(sapp2, func2)) =
4162            (inner1.as_ref(), inner2.as_ref())
4163        {
4164            if let (Term::Global(n1), Term::Global(n2)) = (sapp1.as_ref(), sapp2.as_ref()) {
4165                if n1 == "SApp" && n2 == "SApp" {
4166                    return can_unify_syntax_terms_with_bindings(ctx, func1, func2, pattern_vars, bindings)
4167                        && can_unify_syntax_terms_with_bindings(ctx, arg1.as_ref(), arg2.as_ref(), pattern_vars, bindings);
4168                }
4169            }
4170        }
4171    }
4172
4173    // SLit: compare literal values
4174    if let (Term::App(ctor1, lit1), Term::App(ctor2, lit2)) = (pattern, concrete) {
4175        if let (Term::Global(n1), Term::Global(n2)) = (ctor1.as_ref(), ctor2.as_ref()) {
4176            if n1 == "SLit" && n2 == "SLit" {
4177                return lit1 == lit2;
4178            }
4179        }
4180    }
4181
4182    // Fall back to exact structural equality
4183    pattern == concrete
4184}
4185
4186/// Check if two Syntax terms are structurally equal.
4187fn syntax_terms_equal(a: &Term, b: &Term) -> bool {
4188    match (a, b) {
4189        (Term::App(f1, x1), Term::App(f2, x2)) => {
4190            syntax_terms_equal(f1, f2) && syntax_terms_equal(x1, x2)
4191        }
4192        (Term::Global(n1), Term::Global(n2)) => n1 == n2,
4193        (Term::Lit(l1), Term::Lit(l2)) => l1 == l2,
4194        _ => a == b,
4195    }
4196}
4197
4198// -------------------------------------------------------------------------
4199// Operator Tactics (rewrite, destruct, apply)
4200// -------------------------------------------------------------------------
4201
4202/// Extract Eq A x y components from a Syntax term.
4203///
4204/// SApp (SApp (SApp (SName "Eq") A) x) y → Some((A, x, y))
4205fn extract_eq_components_from_syntax(term: &Term) -> Option<(Term, Term, Term)> {
4206    // term = SApp (SApp (SApp (SName "Eq") A) x) y
4207    // In kernel representation: App(App(SApp, App(App(SApp, App(App(SApp, SName "Eq"), A)), x)), y)
4208
4209    // Peel off outermost SApp to get ((SApp (SApp (SName "Eq") A) x), y)
4210    let (eq_a_x, y) = extract_sapp(term)?;
4211
4212    // Peel off next SApp to get ((SApp (SName "Eq") A), x)
4213    let (eq_a, x) = extract_sapp(&eq_a_x)?;
4214
4215    // Peel off next SApp to get ((SName "Eq"), A)
4216    let (eq, a) = extract_sapp(&eq_a)?;
4217
4218    // Verify it's SName "Eq"
4219    let eq_name = extract_sname(&eq)?;
4220    if eq_name != "Eq" {
4221        return None;
4222    }
4223
4224    Some((a, x, y))
4225}
4226
4227/// Extract (f, x) from SApp f x (in kernel representation).
4228fn extract_sapp(term: &Term) -> Option<(Term, Term)> {
4229    // SApp f x = App(App(Global("SApp"), f), x)
4230    if let Term::App(inner, x) = term {
4231        if let Term::App(sapp_ctor, f) = inner.as_ref() {
4232            if let Term::Global(ctor_name) = sapp_ctor.as_ref() {
4233                if ctor_name == "SApp" {
4234                    return Some((f.as_ref().clone(), x.as_ref().clone()));
4235                }
4236            }
4237        }
4238    }
4239    None
4240}
4241
4242/// Extract name from SName "name".
4243fn extract_sname(term: &Term) -> Option<String> {
4244    if let Term::App(ctor, text) = term {
4245        if let Term::Global(ctor_name) = ctor.as_ref() {
4246            if ctor_name == "SName" {
4247                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
4248                    return Some(name.clone());
4249                }
4250            }
4251        }
4252    }
4253    None
4254}
4255
4256/// Check if a Syntax term contains a specific subterm.
4257fn contains_subterm_syntax(term: &Term, target: &Term) -> bool {
4258    if syntax_equal(term, target) {
4259        return true;
4260    }
4261
4262    // Check SApp f x
4263    if let Some((f, x)) = extract_sapp(term) {
4264        if contains_subterm_syntax(&f, target) || contains_subterm_syntax(&x, target) {
4265            return true;
4266        }
4267    }
4268
4269    // Check SLam T body
4270    if let Some((t, body)) = extract_slam(term) {
4271        if contains_subterm_syntax(&t, target) || contains_subterm_syntax(&body, target) {
4272            return true;
4273        }
4274    }
4275
4276    // Check SPi T body
4277    if let Some((t, body)) = extract_spi(term) {
4278        if contains_subterm_syntax(&t, target) || contains_subterm_syntax(&body, target) {
4279            return true;
4280        }
4281    }
4282
4283    false
4284}
4285
4286/// Extract (T, body) from SLam T body.
4287fn extract_slam(term: &Term) -> Option<(Term, Term)> {
4288    if let Term::App(inner, body) = term {
4289        if let Term::App(slam_ctor, t) = inner.as_ref() {
4290            if let Term::Global(ctor_name) = slam_ctor.as_ref() {
4291                if ctor_name == "SLam" {
4292                    return Some((t.as_ref().clone(), body.as_ref().clone()));
4293                }
4294            }
4295        }
4296    }
4297    None
4298}
4299
4300/// Extract (T, body) from SPi T body.
4301fn extract_spi(term: &Term) -> Option<(Term, Term)> {
4302    if let Term::App(inner, body) = term {
4303        if let Term::App(spi_ctor, t) = inner.as_ref() {
4304            if let Term::Global(ctor_name) = spi_ctor.as_ref() {
4305                if ctor_name == "SPi" {
4306                    return Some((t.as_ref().clone(), body.as_ref().clone()));
4307                }
4308            }
4309        }
4310    }
4311    None
4312}
4313
4314/// Replace first occurrence of target with replacement in a Syntax term.
4315fn replace_first_subterm_syntax(term: &Term, target: &Term, replacement: &Term) -> Option<Term> {
4316    // If term equals target, return replacement
4317    if syntax_equal(term, target) {
4318        return Some(replacement.clone());
4319    }
4320
4321    // Try to replace in SApp f x
4322    if let Some((f, x)) = extract_sapp(term) {
4323        // First try to replace in f
4324        if let Some(new_f) = replace_first_subterm_syntax(&f, target, replacement) {
4325            return Some(make_sapp(new_f, x));
4326        }
4327        // Then try to replace in x
4328        if let Some(new_x) = replace_first_subterm_syntax(&x, target, replacement) {
4329            return Some(make_sapp(f, new_x));
4330        }
4331    }
4332
4333    // Try to replace in SLam T body
4334    if let Some((t, body)) = extract_slam(term) {
4335        if let Some(new_t) = replace_first_subterm_syntax(&t, target, replacement) {
4336            return Some(make_slam(new_t, body));
4337        }
4338        if let Some(new_body) = replace_first_subterm_syntax(&body, target, replacement) {
4339            return Some(make_slam(t, new_body));
4340        }
4341    }
4342
4343    // Try to replace in SPi T body
4344    if let Some((t, body)) = extract_spi(term) {
4345        if let Some(new_t) = replace_first_subterm_syntax(&t, target, replacement) {
4346            return Some(make_spi(new_t, body));
4347        }
4348        if let Some(new_body) = replace_first_subterm_syntax(&body, target, replacement) {
4349            return Some(make_spi(t, new_body));
4350        }
4351    }
4352
4353    // No replacement found
4354    None
4355}
4356
4357/// Rewrite tactic: given eq_proof (concluding Eq A x y) and goal,
4358/// replaces x with y (or y with x if reverse=true) in goal.
4359fn try_try_rewrite_reduce(
4360    ctx: &Context,
4361    eq_proof: &Term,
4362    goal: &Term,
4363    reverse: bool,
4364) -> Option<Term> {
4365    // Get the conclusion of eq_proof
4366    let eq_conclusion = try_concludes_reduce(ctx, eq_proof)?;
4367
4368    // Extract Eq A x y components
4369    let (ty, lhs, rhs) = match extract_eq_components_from_syntax(&eq_conclusion) {
4370        Some(components) => components,
4371        None => return Some(make_error_derivation()),
4372    };
4373
4374    // Determine what to replace based on direction
4375    let (target, replacement) = if reverse { (rhs, lhs) } else { (lhs, rhs) };
4376
4377    // Check if target exists in goal
4378    if !contains_subterm_syntax(goal, &target) {
4379        return Some(make_error_derivation());
4380    }
4381
4382    // Replace target with replacement in goal
4383    let new_goal = match replace_first_subterm_syntax(goal, &target, &replacement) {
4384        Some(ng) => ng,
4385        None => return Some(make_error_derivation()),
4386    };
4387
4388    // Build DRewrite eq_proof goal new_goal
4389    Some(Term::App(
4390        Box::new(Term::App(
4391            Box::new(Term::App(
4392                Box::new(Term::Global("DRewrite".to_string())),
4393                Box::new(eq_proof.clone()),
4394            )),
4395            Box::new(goal.clone()),
4396        )),
4397        Box::new(new_goal),
4398    ))
4399}
4400
4401/// Verify DRewrite derivation and return the new goal.
4402fn try_drewrite_conclude(
4403    ctx: &Context,
4404    eq_proof: &Term,
4405    old_goal: &Term,
4406    new_goal: &Term,
4407) -> Option<Term> {
4408    // Get the conclusion of eq_proof
4409    let eq_conclusion = try_concludes_reduce(ctx, eq_proof)?;
4410
4411    // Extract Eq A x y components
4412    let (_ty, lhs, rhs) = match extract_eq_components_from_syntax(&eq_conclusion) {
4413        Some(components) => components,
4414        None => return Some(make_sname_error()),
4415    };
4416
4417    // Verify: new_goal = old_goal[lhs := rhs] OR new_goal = old_goal[rhs := lhs]
4418    // Check forward direction first
4419    if let Some(computed_new) = replace_first_subterm_syntax(old_goal, &lhs, &rhs) {
4420        if syntax_equal(&computed_new, new_goal) {
4421            return Some(new_goal.clone());
4422        }
4423    }
4424
4425    // Check reverse direction
4426    if let Some(computed_new) = replace_first_subterm_syntax(old_goal, &rhs, &lhs) {
4427        if syntax_equal(&computed_new, new_goal) {
4428            return Some(new_goal.clone());
4429        }
4430    }
4431
4432    // Verification failed
4433    Some(make_sname_error())
4434}
4435
4436/// Destruct tactic: case analysis without induction hypotheses.
4437fn try_try_destruct_reduce(
4438    ctx: &Context,
4439    ind_type: &Term,
4440    motive: &Term,
4441    cases: &Term,
4442) -> Option<Term> {
4443    // For now, destruct is essentially the same as induction
4444    // The key difference is in what goals are expected for each case
4445    // (no IH for recursive constructors)
4446    //
4447    // We simply build a DDestruct and let verification check case proofs
4448
4449    Some(Term::App(
4450        Box::new(Term::App(
4451            Box::new(Term::App(
4452                Box::new(Term::Global("DDestruct".to_string())),
4453                Box::new(ind_type.clone()),
4454            )),
4455            Box::new(motive.clone()),
4456        )),
4457        Box::new(cases.clone()),
4458    ))
4459}
4460
4461/// Verify DDestruct derivation.
4462fn try_ddestruct_conclude(
4463    ctx: &Context,
4464    ind_type: &Term,
4465    motive: &Term,
4466    cases: &Term,
4467) -> Option<Term> {
4468    // Similar to DElim but without verifying IH in step cases
4469    // For now, we accept the derivation and return Forall ind_type motive
4470
4471    // Extract the inductive type name
4472    let ind_name = extract_inductive_name_from_syntax(ind_type)?;
4473
4474    // Verify it's actually an inductive type
4475    if !ctx.is_inductive(&ind_name) {
4476        return Some(make_sname_error());
4477    }
4478
4479    let constructors = ctx.get_constructors(&ind_name);
4480
4481    // Extract case proofs
4482    let case_proofs = match extract_case_proofs(cases) {
4483        Some(proofs) => proofs,
4484        None => return Some(make_sname_error()),
4485    };
4486
4487    // Verify case count matches
4488    if case_proofs.len() != constructors.len() {
4489        return Some(make_sname_error());
4490    }
4491
4492    // For each case, verify the conclusion matches the expected goal (without IH)
4493    // For simplicity, we just check case count matches for now
4494    // Full verification would check each case proves P(ctor args)
4495
4496    // Build Forall ind_type motive
4497    Some(make_forall_syntax_with_type(ind_type, motive))
4498}
4499
4500/// Apply tactic: manual backward chaining.
4501fn try_try_apply_reduce(
4502    ctx: &Context,
4503    hyp_name: &Term,
4504    hyp_proof: &Term,
4505    goal: &Term,
4506) -> Option<Term> {
4507    // Get the conclusion of hyp_proof
4508    let hyp_conclusion = try_concludes_reduce(ctx, hyp_proof)?;
4509
4510    // Check if it's an implication: SPi A B where B doesn't use the bound var
4511    if let Some((antecedent, consequent)) = extract_spi(&hyp_conclusion) {
4512        // Check if consequent matches goal
4513        if syntax_equal(&consequent, goal) {
4514            // Build DApply hyp_name hyp_proof goal antecedent
4515            return Some(Term::App(
4516                Box::new(Term::App(
4517                    Box::new(Term::App(
4518                        Box::new(Term::App(
4519                            Box::new(Term::Global("DApply".to_string())),
4520                            Box::new(hyp_name.clone()),
4521                        )),
4522                        Box::new(hyp_proof.clone()),
4523                    )),
4524                    Box::new(goal.clone()),
4525                )),
4526                Box::new(antecedent),
4527            ));
4528        }
4529    }
4530
4531    // Check if it's a forall that could match
4532    if let Some(forall_body) = extract_forall_body(&hyp_conclusion) {
4533        // Try to match goal with forall body (simple syntactic check)
4534        // For now, if goal appears to be an instance of the forall body, accept it
4535        // Full implementation would do proper unification
4536
4537        // Build DApply with new goal being True (trivially satisfied)
4538        return Some(Term::App(
4539            Box::new(Term::App(
4540                Box::new(Term::App(
4541                    Box::new(Term::App(
4542                        Box::new(Term::Global("DApply".to_string())),
4543                        Box::new(hyp_name.clone()),
4544                    )),
4545                    Box::new(hyp_proof.clone()),
4546                )),
4547                Box::new(goal.clone()),
4548            )),
4549            Box::new(make_sname("True")),
4550        ));
4551    }
4552
4553    // If hypothesis directly matches goal, we're done
4554    if syntax_equal(&hyp_conclusion, goal) {
4555        return Some(Term::App(
4556            Box::new(Term::App(
4557                Box::new(Term::App(
4558                    Box::new(Term::App(
4559                        Box::new(Term::Global("DApply".to_string())),
4560                        Box::new(hyp_name.clone()),
4561                    )),
4562                    Box::new(hyp_proof.clone()),
4563                )),
4564                Box::new(goal.clone()),
4565            )),
4566            Box::new(make_sname("True")),
4567        ));
4568    }
4569
4570    // Cannot apply this hypothesis to this goal
4571    Some(make_error_derivation())
4572}
4573
4574/// Verify DApply derivation.
4575fn try_dapply_conclude(
4576    ctx: &Context,
4577    hyp_name: &Term,
4578    hyp_proof: &Term,
4579    old_goal: &Term,
4580    new_goal: &Term,
4581) -> Option<Term> {
4582    // Get the conclusion of hyp_proof
4583    let hyp_conclusion = try_concludes_reduce(ctx, hyp_proof)?;
4584
4585    // If hypothesis is an implication A -> B and old_goal is B
4586    // then new_goal should be A
4587    if let Some((antecedent, consequent)) = extract_spi(&hyp_conclusion) {
4588        if syntax_equal(&consequent, old_goal) {
4589            if syntax_equal(&antecedent, new_goal) || extract_sname(new_goal) == Some("True".to_string()) {
4590                return Some(new_goal.clone());
4591            }
4592        }
4593    }
4594
4595    // If hypothesis is a forall and goal matches instantiation
4596    if let Some(_forall_body) = extract_forall_body(&hyp_conclusion) {
4597        // For forall application, the new goal is typically True or the instantiated body
4598        if extract_sname(new_goal) == Some("True".to_string()) {
4599            return Some(new_goal.clone());
4600        }
4601    }
4602
4603    // If hypothesis directly matches old_goal
4604    if syntax_equal(&hyp_conclusion, old_goal) {
4605        if extract_sname(new_goal) == Some("True".to_string()) {
4606            return Some(new_goal.clone());
4607        }
4608    }
4609
4610    // Verification failed
4611    Some(make_sname_error())
4612}
4613