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 literals - skip for now as they're rarely used in proofs
1775        Term::Lit(Literal::Float(_)) => None,
1776
1777        // Match expressions, Fix, and Hole are complex - skip for now
1778        Term::Match { .. } | Term::Fix { .. } | Term::Hole => None,
1779    }
1780}
1781
1782/// Build DHint derivation that references a hint by name.
1783///
1784/// Uses DAxiom (SName "hint_name") to indicate the proof uses this hint.
1785fn make_hint_derivation(hint_name: &str, goal: &Term) -> Term {
1786    // Build: DAxiom (SApp (SName "Hint") (SName hint_name))
1787    // This distinguishes hints from error derivations
1788    let hint_marker = make_sapp(make_sname("Hint"), make_sname(hint_name));
1789
1790    // Actually, let's use a simpler approach: DAutoSolve goal
1791    // This indicates auto succeeded, and we can trace which hint was used through debugging
1792    Term::App(
1793        Box::new(Term::Global("DAutoSolve".to_string())),
1794        Box::new(goal.clone()),
1795    )
1796}
1797
1798/// Try to apply a hint to prove a goal.
1799///
1800/// Returns Some(derivation) if the hint's type matches the goal,
1801/// otherwise returns None.
1802fn try_apply_hint(ctx: &Context, hint_name: &str, hint_type: &Term, goal: &Term) -> Option<Term> {
1803    // Convert hint type to syntax form
1804    let hint_syntax = term_to_syntax(hint_type)?;
1805
1806    // Normalize both for comparison
1807    let norm_hint = normalize(ctx, &hint_syntax);
1808    let norm_goal = normalize(ctx, goal);
1809
1810    // Direct match: hint type equals goal
1811    if syntax_equal(&norm_hint, &norm_goal) {
1812        return Some(make_hint_derivation(hint_name, goal));
1813    }
1814
1815    // Try backward chaining for implications: if hint is P → Q and goal is Q,
1816    // try to prove P using auto and then apply the hint
1817    if let Term::App(outer, q) = &hint_syntax {
1818        if let Term::App(pi_ctor, p) = outer.as_ref() {
1819            if let Term::Global(name) = pi_ctor.as_ref() {
1820                if name == "SPi" {
1821                    // hint_type is SPi P Q, goal might be Q
1822                    let norm_q = normalize(ctx, q);
1823                    if syntax_equal(&norm_q, &norm_goal) {
1824                        // Need to prove P first, then apply hint
1825                        // This would require recursive auto call - skip for now
1826                        // to avoid infinite recursion
1827                    }
1828                }
1829            }
1830        }
1831    }
1832
1833    None
1834}
1835
1836/// Build Forall Type0 (SLam Type0 body)
1837fn make_forall_syntax(body: &Term) -> Term {
1838    let type0 = Term::App(
1839        Box::new(Term::Global("SSort".to_string())),
1840        Box::new(Term::App(
1841            Box::new(Term::Global("UType".to_string())),
1842            Box::new(Term::Lit(Literal::Int(0))),
1843        )),
1844    );
1845
1846    // SLam Type0 body
1847    let slam = Term::App(
1848        Box::new(Term::App(
1849            Box::new(Term::Global("SLam".to_string())),
1850            Box::new(type0.clone()),
1851        )),
1852        Box::new(body.clone()),
1853    );
1854
1855    // SApp (SApp (SName "Forall") Type0) slam
1856    Term::App(
1857        Box::new(Term::App(
1858            Box::new(Term::Global("SApp".to_string())),
1859            Box::new(Term::App(
1860                Box::new(Term::App(
1861                    Box::new(Term::Global("SApp".to_string())),
1862                    Box::new(Term::App(
1863                        Box::new(Term::Global("SName".to_string())),
1864                        Box::new(Term::Lit(Literal::Text("Forall".to_string()))),
1865                    )),
1866                )),
1867                Box::new(type0),
1868            )),
1869        )),
1870        Box::new(slam),
1871    )
1872}
1873
1874// -------------------------------------------------------------------------
1875// Core Tactics
1876// -------------------------------------------------------------------------
1877
1878/// Build SApp (SApp (SApp (SName "Eq") type_s) term) term
1879///
1880/// Constructs the Syntax representation of (Eq type_s term term)
1881fn make_eq_syntax(type_s: &Term, term: &Term) -> Term {
1882    let eq_name = Term::App(
1883        Box::new(Term::Global("SName".to_string())),
1884        Box::new(Term::Lit(Literal::Text("Eq".to_string()))),
1885    );
1886
1887    // SApp (SName "Eq") type_s
1888    let app1 = Term::App(
1889        Box::new(Term::App(
1890            Box::new(Term::Global("SApp".to_string())),
1891            Box::new(eq_name),
1892        )),
1893        Box::new(type_s.clone()),
1894    );
1895
1896    // SApp (SApp (SName "Eq") type_s) term
1897    let app2 = Term::App(
1898        Box::new(Term::App(
1899            Box::new(Term::Global("SApp".to_string())),
1900            Box::new(app1),
1901        )),
1902        Box::new(term.clone()),
1903    );
1904
1905    // SApp (SApp (SApp (SName "Eq") type_s) term) term
1906    Term::App(
1907        Box::new(Term::App(
1908            Box::new(Term::Global("SApp".to_string())),
1909            Box::new(app2),
1910        )),
1911        Box::new(term.clone()),
1912    )
1913}
1914
1915/// Extract (T, a, b) from SApp (SApp (SApp (SName "Eq") T) a) b
1916///
1917/// Pattern matches against the Syntax representation of (Eq T a b)
1918fn extract_eq(term: &Term) -> Option<(Term, Term, Term)> {
1919    // term = App(App(SApp, X), b)
1920    if let Term::App(outer, b) = term {
1921        if let Term::App(sapp_outer, x) = outer.as_ref() {
1922            if let Term::Global(ctor) = sapp_outer.as_ref() {
1923                if ctor == "SApp" {
1924                    // x = App(App(SApp, Y), a)
1925                    if let Term::App(inner, a) = x.as_ref() {
1926                        if let Term::App(sapp_inner, y) = inner.as_ref() {
1927                            if let Term::Global(ctor2) = sapp_inner.as_ref() {
1928                                if ctor2 == "SApp" {
1929                                    // y = App(App(SApp, eq_name), t)
1930                                    if let Term::App(inner2, t) = y.as_ref() {
1931                                        if let Term::App(sapp_inner2, sname_eq) = inner2.as_ref() {
1932                                            if let Term::Global(ctor3) = sapp_inner2.as_ref() {
1933                                                if ctor3 == "SApp" {
1934                                                    // sname_eq = App(SName, "Eq")
1935                                                    if let Term::App(sname, text) = sname_eq.as_ref()
1936                                                    {
1937                                                        if let Term::Global(sname_ctor) =
1938                                                            sname.as_ref()
1939                                                        {
1940                                                            if sname_ctor == "SName" {
1941                                                                if let Term::Lit(Literal::Text(s)) =
1942                                                                    text.as_ref()
1943                                                                {
1944                                                                    if s == "Eq" {
1945                                                                        return Some((
1946                                                                            t.as_ref().clone(),
1947                                                                            a.as_ref().clone(),
1948                                                                            b.as_ref().clone(),
1949                                                                        ));
1950                                                                    }
1951                                                                }
1952                                                            }
1953                                                        }
1954                                                    }
1955                                                }
1956                                            }
1957                                        }
1958                                    }
1959                                }
1960                            }
1961                        }
1962                    }
1963                }
1964            }
1965        }
1966    }
1967    None
1968}
1969
1970/// Build DRefl type_s term
1971fn make_drefl(type_s: &Term, term: &Term) -> Term {
1972    let drefl = Term::Global("DRefl".to_string());
1973    let app1 = Term::App(Box::new(drefl), Box::new(type_s.clone()));
1974    Term::App(Box::new(app1), Box::new(term.clone()))
1975}
1976
1977/// Build DAxiom (SName "Error")
1978fn make_error_derivation() -> Term {
1979    let daxiom = Term::Global("DAxiom".to_string());
1980    let error = make_sname_error();
1981    Term::App(Box::new(daxiom), Box::new(error))
1982}
1983
1984/// Build DAxiom goal (identity derivation)
1985fn make_daxiom(goal: &Term) -> Term {
1986    let daxiom = Term::Global("DAxiom".to_string());
1987    Term::App(Box::new(daxiom), Box::new(goal.clone()))
1988}
1989
1990/// Reflexivity tactic: try to prove a goal by reflexivity.
1991///
1992/// try_refl goal:
1993/// - If goal matches (Eq T a b) and a == b, return DRefl T a
1994/// - Otherwise return DAxiom (SName "Error")
1995fn try_try_refl_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
1996    // Normalize the goal first
1997    let norm_goal = normalize(ctx, goal);
1998
1999    // Pattern match: SApp (SApp (SApp (SName "Eq") T) a) b
2000    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2001        // Check if left == right (structural equality)
2002        if syntax_equal(&left, &right) {
2003            // Success! Return DRefl T a
2004            return Some(make_drefl(&type_s, &left));
2005        }
2006    }
2007
2008    // Failure: return error derivation
2009    Some(make_error_derivation())
2010}
2011
2012// =============================================================================
2013// RING TACTIC (POLYNOMIAL EQUALITY)
2014// =============================================================================
2015
2016use crate::ring;
2017use crate::lia;
2018use crate::cc;
2019use crate::simp;
2020
2021/// Ring tactic: try to prove a goal by polynomial normalization.
2022///
2023/// try_ring goal:
2024/// - If goal matches (Eq T a b) where T is Int or Nat
2025/// - And poly(a) == poly(b) after normalization
2026/// - Returns DRingSolve goal
2027/// - Otherwise returns DAxiom (SName "Error")
2028fn try_try_ring_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2029    // Normalize the goal first
2030    let norm_goal = normalize(ctx, goal);
2031
2032    // Pattern match: SApp (SApp (SApp (SName "Eq") T) a) b
2033    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2034        // Check type is Int or Nat (ring types)
2035        if !is_ring_type(&type_s) {
2036            return Some(make_error_derivation());
2037        }
2038
2039        // Reify both sides to polynomials
2040        let poly_left = match ring::reify(&left) {
2041            Ok(p) => p,
2042            Err(_) => return Some(make_error_derivation()),
2043        };
2044        let poly_right = match ring::reify(&right) {
2045            Ok(p) => p,
2046            Err(_) => return Some(make_error_derivation()),
2047        };
2048
2049        // Check canonical equality
2050        if poly_left.canonical_eq(&poly_right) {
2051            // Success! Return DRingSolve goal
2052            return Some(Term::App(
2053                Box::new(Term::Global("DRingSolve".to_string())),
2054                Box::new(norm_goal),
2055            ));
2056        }
2057    }
2058
2059    // Failure: return error derivation
2060    Some(make_error_derivation())
2061}
2062
2063/// Verify DRingSolve proof.
2064///
2065/// DRingSolve goal → goal (if verified)
2066fn try_dring_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2067    let norm_goal = normalize(ctx, goal);
2068
2069    // Extract T, lhs, rhs from Eq T lhs rhs
2070    if let Some((type_s, left, right)) = extract_eq(&norm_goal) {
2071        // Verify type is a ring type
2072        if !is_ring_type(&type_s) {
2073            return Some(make_sname_error());
2074        }
2075
2076        // Reify and verify
2077        let poly_left = match ring::reify(&left) {
2078            Ok(p) => p,
2079            Err(_) => return Some(make_sname_error()),
2080        };
2081        let poly_right = match ring::reify(&right) {
2082            Ok(p) => p,
2083            Err(_) => return Some(make_sname_error()),
2084        };
2085
2086        if poly_left.canonical_eq(&poly_right) {
2087            return Some(norm_goal);
2088        }
2089    }
2090
2091    Some(make_sname_error())
2092}
2093
2094/// Check if a Syntax type is a ring type (Int or Nat)
2095fn is_ring_type(type_term: &Term) -> bool {
2096    if let Some(name) = extract_sname_from_syntax(type_term) {
2097        return name == "Int" || name == "Nat";
2098    }
2099    false
2100}
2101
2102/// Extract name from SName term
2103fn extract_sname_from_syntax(term: &Term) -> Option<String> {
2104    if let Term::App(ctor, arg) = term {
2105        if let Term::Global(name) = ctor.as_ref() {
2106            if name == "SName" {
2107                if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
2108                    return Some(s.clone());
2109                }
2110            }
2111        }
2112    }
2113    None
2114}
2115
2116// =============================================================================
2117// LIA TACTIC (LINEAR INTEGER ARITHMETIC)
2118// =============================================================================
2119
2120/// LIA tactic: try to prove a goal by Fourier-Motzkin elimination.
2121///
2122/// try_lia goal:
2123/// - If goal matches (Lt/Le/Gt/Ge a b)
2124/// - And Fourier-Motzkin shows the negation is unsatisfiable
2125/// - Returns DLiaSolve goal
2126/// - Otherwise returns DAxiom (SName "Error")
2127fn try_try_lia_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2128    // Normalize the goal first
2129    let norm_goal = normalize(ctx, goal);
2130
2131    // Extract comparison: (SApp (SApp (SName "Lt"|"Le"|etc) a) b)
2132    if let Some((rel, lhs_term, rhs_term)) = lia::extract_comparison(&norm_goal) {
2133        // Reify both sides to linear expressions
2134        let lhs = match lia::reify_linear(&lhs_term) {
2135            Ok(l) => l,
2136            Err(_) => return Some(make_error_derivation()),
2137        };
2138        let rhs = match lia::reify_linear(&rhs_term) {
2139            Ok(r) => r,
2140            Err(_) => return Some(make_error_derivation()),
2141        };
2142
2143        // Convert to negated constraint for validity checking
2144        if let Some(negated) = lia::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2145            // If the negation is unsatisfiable, the goal is valid
2146            if lia::fourier_motzkin_unsat(&[negated]) {
2147                // Success! Return DLiaSolve goal
2148                return Some(Term::App(
2149                    Box::new(Term::Global("DLiaSolve".to_string())),
2150                    Box::new(norm_goal),
2151                ));
2152            }
2153        }
2154    }
2155
2156    // Failure: return error derivation
2157    Some(make_error_derivation())
2158}
2159
2160/// Verify DLiaSolve proof.
2161///
2162/// DLiaSolve goal → goal (if verified)
2163fn try_dlia_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2164    let norm_goal = normalize(ctx, goal);
2165
2166    // Extract comparison and verify
2167    if let Some((rel, lhs_term, rhs_term)) = lia::extract_comparison(&norm_goal) {
2168        // Reify both sides
2169        let lhs = match lia::reify_linear(&lhs_term) {
2170            Ok(l) => l,
2171            Err(_) => return Some(make_sname_error()),
2172        };
2173        let rhs = match lia::reify_linear(&rhs_term) {
2174            Ok(r) => r,
2175            Err(_) => return Some(make_sname_error()),
2176        };
2177
2178        // Verify via Fourier-Motzkin
2179        if let Some(negated) = lia::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2180            if lia::fourier_motzkin_unsat(&[negated]) {
2181                return Some(norm_goal);
2182            }
2183        }
2184    }
2185
2186    Some(make_sname_error())
2187}
2188
2189// =============================================================================
2190// CONGRUENCE CLOSURE TACTIC
2191// =============================================================================
2192
2193/// CC tactic: try to prove a goal by congruence closure.
2194///
2195/// try_cc goal:
2196/// - If goal is a direct equality (Eq a b) or an implication with hypotheses
2197/// - And congruence closure shows the conclusion follows
2198/// - Returns DccSolve goal
2199/// - Otherwise returns DAxiom (SName "Error")
2200fn try_try_cc_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2201    // Normalize the goal first
2202    let norm_goal = normalize(ctx, goal);
2203
2204    // Use cc::check_goal which handles both:
2205    // - Direct equalities: (Eq a b)
2206    // - Implications: (implies (Eq x y) (Eq (f x) (f y)))
2207    if cc::check_goal(&norm_goal) {
2208        // Success! Return DccSolve goal
2209        return Some(Term::App(
2210            Box::new(Term::Global("DccSolve".to_string())),
2211            Box::new(norm_goal),
2212        ));
2213    }
2214
2215    // Failure: return error derivation
2216    Some(make_error_derivation())
2217}
2218
2219/// Verify DccSolve proof.
2220///
2221/// DccSolve goal → goal (if verified)
2222fn try_dcc_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2223    let norm_goal = normalize(ctx, goal);
2224
2225    // Re-verify using congruence closure
2226    if cc::check_goal(&norm_goal) {
2227        return Some(norm_goal);
2228    }
2229
2230    Some(make_sname_error())
2231}
2232
2233// =============================================================================
2234// SIMP TACTIC (SIMPLIFICATION)
2235// =============================================================================
2236
2237/// try_simp goal:
2238/// - Prove equalities by simplification and arithmetic evaluation
2239/// - Handles hypotheses via implications: (implies (Eq x 0) (Eq (add x 1) 1))
2240/// - Returns DSimpSolve goal on success
2241/// - Otherwise returns DAxiom (SName "Error")
2242fn try_try_simp_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2243    // Normalize the goal first
2244    let norm_goal = normalize(ctx, goal);
2245
2246    // Use simp::check_goal which handles:
2247    // - Reflexive equalities: (Eq a a)
2248    // - Constant folding: (Eq (add 2 3) 5)
2249    // - Hypothesis substitution: (implies (Eq x 0) (Eq (add x 1) 1))
2250    if simp::check_goal(&norm_goal) {
2251        // Success! Return DSimpSolve goal
2252        return Some(Term::App(
2253            Box::new(Term::Global("DSimpSolve".to_string())),
2254            Box::new(norm_goal),
2255        ));
2256    }
2257
2258    // Failure: return error derivation
2259    Some(make_error_derivation())
2260}
2261
2262/// Verify DSimpSolve proof.
2263///
2264/// DSimpSolve goal → goal (if verified)
2265fn try_dsimp_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2266    let norm_goal = normalize(ctx, goal);
2267
2268    // Re-verify using simplification
2269    if simp::check_goal(&norm_goal) {
2270        return Some(norm_goal);
2271    }
2272
2273    Some(make_sname_error())
2274}
2275
2276// =============================================================================
2277// OMEGA TACTIC (TRUE INTEGER ARITHMETIC)
2278// =============================================================================
2279
2280/// try_omega goal:
2281///
2282/// Omega tactic using integer-aware Fourier-Motzkin elimination.
2283/// Unlike lia (which uses rationals), omega handles integers properly:
2284/// - x > 1 means x >= 2 (strict-to-nonstrict conversion)
2285/// - 3x <= 10 means x <= 3 (floor division)
2286///
2287/// - Extracts comparison (Lt, Le, Gt, Ge) from goal
2288/// - Reifies to integer linear expressions
2289/// - Converts to negated constraint (validity = negation is unsat)
2290/// - Applies omega test
2291/// - Returns DOmegaSolve goal on success
2292fn try_try_omega_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2293    let norm_goal = normalize(ctx, goal);
2294
2295    // Handle implications: extract hypotheses and check conclusion
2296    if let Some((hyps, conclusion)) = extract_implications(&norm_goal) {
2297        // Convert hypotheses to constraints
2298        let mut constraints = Vec::new();
2299
2300        for hyp in &hyps {
2301            // Try to extract a comparison from the hypothesis
2302            if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(hyp) {
2303                if let (Some(lhs), Some(rhs)) =
2304                    (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2305                {
2306                    // Add hypothesis as a constraint (it's given, so it's a fact)
2307                    // For Lt(a, b): a - b < 0, i.e., (a - b) < 0
2308                    // Constraint form: (a - b + 1) <= 0 for integers (since a < b means a <= b - 1)
2309                    match rel.as_str() {
2310                        "Lt" | "lt" => {
2311                            // a < b means a - b <= -1, i.e., (a - b + 1) <= 0
2312                            let mut expr = lhs.sub(&rhs);
2313                            expr.constant += 1;
2314                            constraints.push(omega::IntConstraint { expr, strict: false });
2315                        }
2316                        "Le" | "le" => {
2317                            // a <= b means a - b <= 0
2318                            constraints.push(omega::IntConstraint {
2319                                expr: lhs.sub(&rhs),
2320                                strict: false,
2321                            });
2322                        }
2323                        "Gt" | "gt" => {
2324                            // a > b means a - b >= 1, i.e., (b - a + 1) <= 0
2325                            let mut expr = rhs.sub(&lhs);
2326                            expr.constant += 1;
2327                            constraints.push(omega::IntConstraint { expr, strict: false });
2328                        }
2329                        "Ge" | "ge" => {
2330                            // a >= b means b - a <= 0
2331                            constraints.push(omega::IntConstraint {
2332                                expr: rhs.sub(&lhs),
2333                                strict: false,
2334                            });
2335                        }
2336                        _ => {}
2337                    }
2338                }
2339            }
2340        }
2341
2342        // Now check if the conclusion is provable given the constraints
2343        if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&conclusion) {
2344            if let (Some(lhs), Some(rhs)) =
2345                (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2346            {
2347                // To prove the conclusion, check if its negation is unsat
2348                if let Some(neg_constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2349                    let mut all_constraints = constraints;
2350                    all_constraints.push(neg_constraint);
2351
2352                    if omega::omega_unsat(&all_constraints) {
2353                        return Some(Term::App(
2354                            Box::new(Term::Global("DOmegaSolve".to_string())),
2355                            Box::new(norm_goal),
2356                        ));
2357                    }
2358                }
2359            }
2360        }
2361
2362        // Failure
2363        return Some(make_error_derivation());
2364    }
2365
2366    // Direct comparison (no hypotheses)
2367    if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&norm_goal) {
2368        if let (Some(lhs), Some(rhs)) =
2369            (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2370        {
2371            if let Some(constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2372                if omega::omega_unsat(&[constraint]) {
2373                    return Some(Term::App(
2374                        Box::new(Term::Global("DOmegaSolve".to_string())),
2375                        Box::new(norm_goal),
2376                    ));
2377                }
2378            }
2379        }
2380    }
2381
2382    Some(make_error_derivation())
2383}
2384
2385/// Verify DOmegaSolve proof.
2386///
2387/// DOmegaSolve goal → goal (if verified)
2388fn try_domega_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2389    let norm_goal = normalize(ctx, goal);
2390
2391    // Re-verify using omega test
2392    // Handle implications
2393    if let Some((hyps, conclusion)) = extract_implications(&norm_goal) {
2394        let mut constraints = Vec::new();
2395
2396        for hyp in &hyps {
2397            if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(hyp) {
2398                if let (Some(lhs), Some(rhs)) =
2399                    (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2400                {
2401                    match rel.as_str() {
2402                        "Lt" | "lt" => {
2403                            let mut expr = lhs.sub(&rhs);
2404                            expr.constant += 1;
2405                            constraints.push(omega::IntConstraint { expr, strict: false });
2406                        }
2407                        "Le" | "le" => {
2408                            constraints.push(omega::IntConstraint {
2409                                expr: lhs.sub(&rhs),
2410                                strict: false,
2411                            });
2412                        }
2413                        "Gt" | "gt" => {
2414                            let mut expr = rhs.sub(&lhs);
2415                            expr.constant += 1;
2416                            constraints.push(omega::IntConstraint { expr, strict: false });
2417                        }
2418                        "Ge" | "ge" => {
2419                            constraints.push(omega::IntConstraint {
2420                                expr: rhs.sub(&lhs),
2421                                strict: false,
2422                            });
2423                        }
2424                        _ => {}
2425                    }
2426                }
2427            }
2428        }
2429
2430        if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&conclusion) {
2431            if let (Some(lhs), Some(rhs)) =
2432                (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2433            {
2434                if let Some(neg_constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2435                    let mut all_constraints = constraints;
2436                    all_constraints.push(neg_constraint);
2437
2438                    if omega::omega_unsat(&all_constraints) {
2439                        return Some(norm_goal);
2440                    }
2441                }
2442            }
2443        }
2444
2445        return Some(make_sname_error());
2446    }
2447
2448    // Direct comparison
2449    if let Some((rel, lhs_term, rhs_term)) = omega::extract_comparison(&norm_goal) {
2450        if let (Some(lhs), Some(rhs)) =
2451            (omega::reify_int_linear(&lhs_term), omega::reify_int_linear(&rhs_term))
2452        {
2453            if let Some(constraint) = omega::goal_to_negated_constraint(&rel, &lhs, &rhs) {
2454                if omega::omega_unsat(&[constraint]) {
2455                    return Some(norm_goal);
2456                }
2457            }
2458        }
2459    }
2460
2461    Some(make_sname_error())
2462}
2463
2464// =============================================================================
2465// AUTO TACTIC (THE INFINITY GAUNTLET)
2466// =============================================================================
2467
2468/// Check if a derivation is an error derivation.
2469///
2470/// Error derivations have the pattern: DAxiom (SApp (SName "Error") ...)
2471fn is_error_derivation(term: &Term) -> bool {
2472    // Check for DAxiom constructor
2473    if let Term::App(ctor, arg) = term {
2474        if let Term::Global(name) = ctor.as_ref() {
2475            if name == "DAxiom" {
2476                // Check if arg is SName "Error" or SApp (SName "Error") ...
2477                if let Term::App(sname, inner) = arg.as_ref() {
2478                    if let Term::Global(sn) = sname.as_ref() {
2479                        if sn == "SName" {
2480                            if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2481                                return s == "Error";
2482                            }
2483                        }
2484                        if sn == "SApp" {
2485                            // Recursively check - could be SApp (SName "Error") ...
2486                            return true; // For now, treat any DAxiom as potential error
2487                        }
2488                    }
2489                }
2490                // DAxiom with SName "Error"
2491                if let Term::Global(sn) = arg.as_ref() {
2492                    // This shouldn't happen but just in case
2493                    return sn == "Error";
2494                }
2495                return true; // Any DAxiom is treated as error for auto purposes
2496            }
2497        }
2498    }
2499    false
2500}
2501
2502/// Auto tactic: tries each decision procedure in sequence.
2503///
2504/// Order: True/False → simp → ring → cc → omega → lia
2505/// Returns the first successful derivation, or error if all fail.
2506fn try_try_auto_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
2507    let norm_goal = normalize(ctx, goal);
2508
2509    // Handle trivial cases: True and False
2510    // SName "True" is trivially provable
2511    if let Term::App(ctor, inner) = &norm_goal {
2512        if let Term::Global(name) = ctor.as_ref() {
2513            if name == "SName" {
2514                if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2515                    if s == "True" {
2516                        // True is always provable - use DAutoSolve
2517                        return Some(Term::App(
2518                            Box::new(Term::Global("DAutoSolve".to_string())),
2519                            Box::new(norm_goal),
2520                        ));
2521                    }
2522                    if s == "False" {
2523                        // False is never provable
2524                        return Some(make_error_derivation());
2525                    }
2526                }
2527            }
2528        }
2529    }
2530
2531    // Try simp (handles equalities with simplification)
2532    if let Some(result) = try_try_simp_reduce(ctx, &norm_goal) {
2533        if !is_error_derivation(&result) {
2534            return Some(result);
2535        }
2536    }
2537
2538    // Try ring (polynomial equalities)
2539    if let Some(result) = try_try_ring_reduce(ctx, &norm_goal) {
2540        if !is_error_derivation(&result) {
2541            return Some(result);
2542        }
2543    }
2544
2545    // Try cc (congruence closure)
2546    if let Some(result) = try_try_cc_reduce(ctx, &norm_goal) {
2547        if !is_error_derivation(&result) {
2548            return Some(result);
2549        }
2550    }
2551
2552    // Try omega (integer arithmetic - most precise)
2553    if let Some(result) = try_try_omega_reduce(ctx, &norm_goal) {
2554        if !is_error_derivation(&result) {
2555            return Some(result);
2556        }
2557    }
2558
2559    // Try lia (linear arithmetic - fallback)
2560    if let Some(result) = try_try_lia_reduce(ctx, &norm_goal) {
2561        if !is_error_derivation(&result) {
2562            return Some(result);
2563        }
2564    }
2565
2566    // Try registered hints
2567    for hint_name in ctx.get_hints() {
2568        if let Some(hint_type) = ctx.get_global(hint_name) {
2569            if let Some(result) = try_apply_hint(ctx, hint_name, hint_type, &norm_goal) {
2570                return Some(result);
2571            }
2572        }
2573    }
2574
2575    // All tactics failed
2576    Some(make_error_derivation())
2577}
2578
2579/// Verify DAutoSolve proof by re-running tactic search.
2580fn try_dauto_solve_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2581    let norm_goal = normalize(ctx, goal);
2582
2583    // Handle trivial cases: True
2584    if let Term::App(ctor, inner) = &norm_goal {
2585        if let Term::Global(name) = ctor.as_ref() {
2586            if name == "SName" {
2587                if let Term::Lit(Literal::Text(s)) = inner.as_ref() {
2588                    if s == "True" {
2589                        return Some(norm_goal.clone());
2590                    }
2591                }
2592            }
2593        }
2594    }
2595
2596    // Try each tactic - if any succeeds, the proof is valid
2597    if let Some(result) = try_try_simp_reduce(ctx, &norm_goal) {
2598        if !is_error_derivation(&result) {
2599            return Some(norm_goal.clone());
2600        }
2601    }
2602    if let Some(result) = try_try_ring_reduce(ctx, &norm_goal) {
2603        if !is_error_derivation(&result) {
2604            return Some(norm_goal.clone());
2605        }
2606    }
2607    if let Some(result) = try_try_cc_reduce(ctx, &norm_goal) {
2608        if !is_error_derivation(&result) {
2609            return Some(norm_goal.clone());
2610        }
2611    }
2612    if let Some(result) = try_try_omega_reduce(ctx, &norm_goal) {
2613        if !is_error_derivation(&result) {
2614            return Some(norm_goal.clone());
2615        }
2616    }
2617    if let Some(result) = try_try_lia_reduce(ctx, &norm_goal) {
2618        if !is_error_derivation(&result) {
2619            return Some(norm_goal.clone());
2620        }
2621    }
2622
2623    // Try registered hints
2624    for hint_name in ctx.get_hints() {
2625        if let Some(hint_type) = ctx.get_global(hint_name) {
2626            if try_apply_hint(ctx, hint_name, hint_type, &norm_goal).is_some() {
2627                return Some(norm_goal);
2628            }
2629        }
2630    }
2631
2632    Some(make_sname_error())
2633}
2634
2635// =============================================================================
2636// GENERIC INDUCTION HELPERS (induction_num_cases, induction_base_goal, etc.)
2637// =============================================================================
2638
2639/// Returns the number of constructors for an inductive type.
2640///
2641/// induction_num_cases (SName "Nat") → Succ (Succ Zero) = 2
2642/// induction_num_cases (SName "Bool") → Succ (Succ Zero) = 2
2643/// induction_num_cases (SApp (SName "List") A) → Succ (Succ Zero) = 2
2644fn try_induction_num_cases_reduce(ctx: &Context, ind_type: &Term) -> Option<Term> {
2645    // Extract inductive name from Syntax
2646    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2647        Some(name) => name,
2648        None => {
2649            // Not a valid inductive type syntax
2650            return Some(Term::Global("Zero".to_string()));
2651        }
2652    };
2653
2654    // Look up constructors
2655    let constructors = ctx.get_constructors(&ind_name);
2656    let num_ctors = constructors.len();
2657
2658    // Build Nat representation: Succ (Succ (... Zero))
2659    let mut result = Term::Global("Zero".to_string());
2660    for _ in 0..num_ctors {
2661        result = Term::App(
2662            Box::new(Term::Global("Succ".to_string())),
2663            Box::new(result),
2664        );
2665    }
2666
2667    Some(result)
2668}
2669
2670/// Returns the base case goal for induction (first constructor).
2671///
2672/// Given ind_type and motive (SLam T body), returns motive[ctor0/var].
2673fn try_induction_base_goal_reduce(
2674    ctx: &Context,
2675    ind_type: &Term,
2676    motive: &Term,
2677) -> Option<Term> {
2678    // Extract inductive name
2679    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2680        Some(name) => name,
2681        None => return Some(make_sname_error()),
2682    };
2683
2684    // Get constructors
2685    let constructors = ctx.get_constructors(&ind_name);
2686    if constructors.is_empty() {
2687        return Some(make_sname_error());
2688    }
2689
2690    // Extract motive body
2691    let motive_body = match extract_slam_body(motive) {
2692        Some(body) => body,
2693        None => return Some(make_sname_error()),
2694    };
2695
2696    // Build goal for first constructor (base case)
2697    let (ctor_name, _) = constructors[0];
2698    build_case_expected(ctx, ctor_name, &constructors, &motive_body, ind_type)
2699}
2700
2701/// Returns the goal for a specific constructor index.
2702///
2703/// Given ind_type, motive, and constructor index (as Nat), returns the case goal.
2704fn try_induction_step_goal_reduce(
2705    ctx: &Context,
2706    ind_type: &Term,
2707    motive: &Term,
2708    ctor_idx: &Term,
2709) -> Option<Term> {
2710    // Extract inductive name
2711    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2712        Some(name) => name,
2713        None => return Some(make_sname_error()),
2714    };
2715
2716    // Get constructors
2717    let constructors = ctx.get_constructors(&ind_name);
2718    if constructors.is_empty() {
2719        return Some(make_sname_error());
2720    }
2721
2722    // Convert Nat to index
2723    let idx = nat_to_usize(ctor_idx)?;
2724    if idx >= constructors.len() {
2725        return Some(make_sname_error());
2726    }
2727
2728    // Extract motive body
2729    let motive_body = match extract_slam_body(motive) {
2730        Some(body) => body,
2731        None => return Some(make_sname_error()),
2732    };
2733
2734    // Build goal for the specified constructor
2735    let (ctor_name, _) = constructors[idx];
2736    build_case_expected(ctx, ctor_name, &constructors, &motive_body, ind_type)
2737}
2738
2739/// Convert a Nat term to usize.
2740///
2741/// Zero → 0
2742/// Succ Zero → 1
2743/// Succ (Succ Zero) → 2
2744fn nat_to_usize(term: &Term) -> Option<usize> {
2745    match term {
2746        Term::Global(name) if name == "Zero" => Some(0),
2747        Term::App(succ, inner) => {
2748            if let Term::Global(name) = succ.as_ref() {
2749                if name == "Succ" {
2750                    return nat_to_usize(inner).map(|n| n + 1);
2751                }
2752            }
2753            None
2754        }
2755        _ => None,
2756    }
2757}
2758
2759/// Generic induction tactic.
2760///
2761/// try_induction ind_type motive cases → DElim ind_type motive cases
2762///
2763/// Delegates to existing DElim infrastructure after basic validation.
2764fn try_try_induction_reduce(
2765    ctx: &Context,
2766    ind_type: &Term,
2767    motive: &Term,
2768    cases: &Term,
2769) -> Option<Term> {
2770    // Extract inductive name to validate
2771    let ind_name = match extract_inductive_name_from_syntax(ind_type) {
2772        Some(name) => name,
2773        None => return Some(make_error_derivation()),
2774    };
2775
2776    // Get constructors to validate count
2777    let constructors = ctx.get_constructors(&ind_name);
2778    if constructors.is_empty() {
2779        return Some(make_error_derivation());
2780    }
2781
2782    // Extract case proofs to validate count
2783    let case_proofs = match extract_case_proofs(cases) {
2784        Some(proofs) => proofs,
2785        None => return Some(make_error_derivation()),
2786    };
2787
2788    // Verify case count matches constructor count
2789    if case_proofs.len() != constructors.len() {
2790        return Some(make_error_derivation());
2791    }
2792
2793    // Build DElim term (delegates verification to existing infrastructure)
2794    Some(Term::App(
2795        Box::new(Term::App(
2796            Box::new(Term::App(
2797                Box::new(Term::Global("DElim".to_string())),
2798                Box::new(ind_type.clone()),
2799            )),
2800            Box::new(motive.clone()),
2801        )),
2802        Box::new(cases.clone()),
2803    ))
2804}
2805
2806// -------------------------------------------------------------------------
2807// Deep Induction
2808// -------------------------------------------------------------------------
2809
2810/// DInduction reduction with verification.
2811///
2812/// DInduction motive base step → Forall Nat motive (if verified)
2813///
2814/// Verification:
2815/// 1. Extract motive body from SLam Nat body
2816/// 2. Check that concludes(base) = motive[Zero/0]
2817/// 3. Check that concludes(step) = ∀k:Nat. P(k) → P(Succ k)
2818/// 4. If all checks pass, return Forall Nat motive
2819/// 5. Otherwise, return Error
2820fn try_dinduction_reduce(
2821    ctx: &Context,
2822    motive: &Term,
2823    base: &Term,
2824    step: &Term,
2825) -> Option<Term> {
2826    // Normalize all inputs
2827    let norm_motive = normalize(ctx, motive);
2828    let norm_base = normalize(ctx, base);
2829    let norm_step = normalize(ctx, step);
2830
2831    // 1. Extract motive body (should be SLam (SName "Nat") body)
2832    let motive_body = match extract_slam_body(&norm_motive) {
2833        Some(body) => body,
2834        None => return Some(make_sname_error()),
2835    };
2836
2837    // 2. Compute expected base: motive body with Zero substituted for SVar 0
2838    let zero = make_sname("Zero");
2839    let expected_base = match try_syn_subst_reduce(ctx, &zero, 0, &motive_body) {
2840        Some(b) => b,
2841        None => return Some(make_sname_error()),
2842    };
2843
2844    // 3. Get actual base conclusion
2845    let base_conc = match try_concludes_reduce(ctx, &norm_base) {
2846        Some(c) => c,
2847        None => return Some(make_sname_error()),
2848    };
2849
2850    // 4. Verify base matches expected
2851    if !syntax_equal(&base_conc, &expected_base) {
2852        return Some(make_sname_error());
2853    }
2854
2855    // 5. Build expected step formula: ∀k:Nat. P(k) → P(Succ k)
2856    let expected_step = match build_induction_step_formula(ctx, &motive_body) {
2857        Some(s) => s,
2858        None => return Some(make_sname_error()),
2859    };
2860
2861    // 6. Get actual step conclusion
2862    let step_conc = match try_concludes_reduce(ctx, &norm_step) {
2863        Some(c) => c,
2864        None => return Some(make_sname_error()),
2865    };
2866
2867    // 7. Verify step matches expected
2868    if !syntax_equal(&step_conc, &expected_step) {
2869        return Some(make_sname_error());
2870    }
2871
2872    // 8. Return conclusion: Forall Nat motive
2873    Some(make_forall_nat_syntax(&norm_motive))
2874}
2875
2876/// Build step formula: ∀k:Nat. P(k) → P(Succ k)
2877///
2878/// Given motive body P (which uses SVar 0 for k), builds:
2879/// Forall (SName "Nat") (SLam (SName "Nat") (Implies P P[Succ(SVar 0)/SVar 0]))
2880fn build_induction_step_formula(ctx: &Context, motive_body: &Term) -> Option<Term> {
2881    // P(k) = motive_body (uses SVar 0 for k)
2882    let p_k = motive_body.clone();
2883
2884    // P(Succ k) = motive_body with (SApp (SName "Succ") (SVar 0)) substituted
2885    let succ_var = Term::App(
2886        Box::new(Term::App(
2887            Box::new(Term::Global("SApp".to_string())),
2888            Box::new(make_sname("Succ")),
2889        )),
2890        Box::new(Term::App(
2891            Box::new(Term::Global("SVar".to_string())),
2892            Box::new(Term::Lit(Literal::Int(0))),
2893        )),
2894    );
2895    let p_succ_k = try_syn_subst_reduce(ctx, &succ_var, 0, motive_body)?;
2896
2897    // Implies P(k) P(Succ k)
2898    let implies_body = make_implies_syntax(&p_k, &p_succ_k);
2899
2900    // SLam (SName "Nat") implies_body
2901    let slam = Term::App(
2902        Box::new(Term::App(
2903            Box::new(Term::Global("SLam".to_string())),
2904            Box::new(make_sname("Nat")),
2905        )),
2906        Box::new(implies_body),
2907    );
2908
2909    // Forall (SName "Nat") slam
2910    Some(make_forall_syntax_with_type(&make_sname("Nat"), &slam))
2911}
2912
2913/// Build SApp (SApp (SName "Implies") a) b
2914fn make_implies_syntax(a: &Term, b: &Term) -> Term {
2915    // SApp (SName "Implies") a
2916    let app1 = Term::App(
2917        Box::new(Term::App(
2918            Box::new(Term::Global("SApp".to_string())),
2919            Box::new(make_sname("Implies")),
2920        )),
2921        Box::new(a.clone()),
2922    );
2923
2924    // SApp (SApp (SName "Implies") a) b
2925    Term::App(
2926        Box::new(Term::App(
2927            Box::new(Term::Global("SApp".to_string())),
2928            Box::new(app1),
2929        )),
2930        Box::new(b.clone()),
2931    )
2932}
2933
2934/// Build SApp (SApp (SName "Forall") (SName "Nat")) motive
2935fn make_forall_nat_syntax(motive: &Term) -> Term {
2936    make_forall_syntax_with_type(&make_sname("Nat"), motive)
2937}
2938
2939/// Build SApp (SApp (SName "Forall") type_s) body
2940fn make_forall_syntax_with_type(type_s: &Term, body: &Term) -> Term {
2941    // SApp (SName "Forall") type_s
2942    let app1 = Term::App(
2943        Box::new(Term::App(
2944            Box::new(Term::Global("SApp".to_string())),
2945            Box::new(make_sname("Forall")),
2946        )),
2947        Box::new(type_s.clone()),
2948    );
2949
2950    // SApp (SApp (SName "Forall") type_s) body
2951    Term::App(
2952        Box::new(Term::App(
2953            Box::new(Term::Global("SApp".to_string())),
2954            Box::new(app1),
2955        )),
2956        Box::new(body.clone()),
2957    )
2958}
2959
2960// -------------------------------------------------------------------------
2961// Solver (Computational Reflection)
2962// -------------------------------------------------------------------------
2963
2964/// DCompute reduction with verification.
2965///
2966/// DCompute goal → goal (if verified by computation)
2967///
2968/// Verification:
2969/// 1. Check that goal is (Eq T A B) as Syntax
2970/// 2. Evaluate A and B using syn_eval with bounded fuel
2971/// 3. If eval(A) == eval(B), return goal
2972/// 4. Otherwise, return Error
2973fn try_dcompute_conclude(ctx: &Context, goal: &Term) -> Option<Term> {
2974    let norm_goal = normalize(ctx, goal);
2975
2976    // Extract T, A, B from Eq T A B (as Syntax)
2977    // Pattern: SApp (SApp (SApp (SName "Eq") T) A) B
2978    let parts = extract_eq_syntax_parts(&norm_goal);
2979    if parts.is_none() {
2980        // Goal is not an equality - return Error
2981        return Some(make_sname_error());
2982    }
2983    let (_, a, b) = parts.unwrap();
2984
2985    // Evaluate A and B with generous fuel
2986    let fuel = 1000;
2987    let a_eval = match try_syn_eval_reduce(ctx, fuel, &a) {
2988        Some(e) => e,
2989        None => return Some(make_sname_error()),
2990    };
2991    let b_eval = match try_syn_eval_reduce(ctx, fuel, &b) {
2992        Some(e) => e,
2993        None => return Some(make_sname_error()),
2994    };
2995
2996    // Compare normalized results
2997    if syntax_equal(&a_eval, &b_eval) {
2998        Some(norm_goal)
2999    } else {
3000        Some(make_sname_error())
3001    }
3002}
3003
3004/// Extract T, A, B from Eq T A B (as Syntax)
3005///
3006/// Pattern: SApp (SApp (SApp (SName "Eq") T) A) B
3007fn extract_eq_syntax_parts(term: &Term) -> Option<(Term, Term, Term)> {
3008    // term = SApp X B where X = SApp Y A where Y = SApp (SName "Eq") T
3009    // Structure: App(App(SApp, App(App(SApp, App(App(SApp, App(SName, "Eq")), T)), A)), B)
3010    if let Term::App(partial2, b) = term {
3011        if let Term::App(sapp2, inner2) = partial2.as_ref() {
3012            if let Term::Global(sapp2_name) = sapp2.as_ref() {
3013                if sapp2_name != "SApp" {
3014                    return None;
3015                }
3016            } else {
3017                return None;
3018            }
3019
3020            if let Term::App(partial1, a) = inner2.as_ref() {
3021                if let Term::App(sapp1, inner1) = partial1.as_ref() {
3022                    if let Term::Global(sapp1_name) = sapp1.as_ref() {
3023                        if sapp1_name != "SApp" {
3024                            return None;
3025                        }
3026                    } else {
3027                        return None;
3028                    }
3029
3030                    if let Term::App(eq_t, t) = inner1.as_ref() {
3031                        if let Term::App(sapp0, eq_sname) = eq_t.as_ref() {
3032                            if let Term::Global(sapp0_name) = sapp0.as_ref() {
3033                                if sapp0_name != "SApp" {
3034                                    return None;
3035                                }
3036                            } else {
3037                                return None;
3038                            }
3039
3040                            // Check if eq_sname is SName "Eq"
3041                            if let Term::App(sname_ctor, eq_str) = eq_sname.as_ref() {
3042                                if let Term::Global(ctor) = sname_ctor.as_ref() {
3043                                    if ctor == "SName" {
3044                                        if let Term::Lit(Literal::Text(s)) = eq_str.as_ref() {
3045                                            if s == "Eq" {
3046                                                return Some((
3047                                                    t.as_ref().clone(),
3048                                                    a.as_ref().clone(),
3049                                                    b.as_ref().clone(),
3050                                                ));
3051                                            }
3052                                        }
3053                                    }
3054                                }
3055                            }
3056                        }
3057                    }
3058                }
3059            }
3060        }
3061    }
3062    None
3063}
3064
3065// -------------------------------------------------------------------------
3066// Tactic Combinators
3067// -------------------------------------------------------------------------
3068
3069/// Reduce tact_orelse t1 t2 goal
3070///
3071/// - Apply t1 to goal
3072/// - If concludes returns Error, apply t2 to goal
3073/// - Otherwise return t1's result
3074fn try_tact_orelse_reduce(
3075    ctx: &Context,
3076    t1: &Term,
3077    t2: &Term,
3078    goal: &Term,
3079) -> Option<Term> {
3080    let norm_goal = normalize(ctx, goal);
3081
3082    // Apply t1 to goal
3083    let d1_app = Term::App(Box::new(t1.clone()), Box::new(norm_goal.clone()));
3084    let d1 = normalize(ctx, &d1_app);
3085
3086    // Check if t1 succeeded by looking at concludes
3087    if let Some(conc1) = try_concludes_reduce(ctx, &d1) {
3088        if is_error_syntax(&conc1) {
3089            // t1 failed, try t2
3090            let d2_app = Term::App(Box::new(t2.clone()), Box::new(norm_goal));
3091            return Some(normalize(ctx, &d2_app));
3092        } else {
3093            // t1 succeeded
3094            return Some(d1);
3095        }
3096    }
3097
3098    // Couldn't evaluate concludes - return error
3099    Some(make_error_derivation())
3100}
3101
3102/// Check if a Syntax term is SName "Error"
3103fn is_error_syntax(term: &Term) -> bool {
3104    // Pattern: SName "Error" = App(Global("SName"), Lit(Text("Error")))
3105    if let Term::App(ctor, arg) = term {
3106        if let Term::Global(name) = ctor.as_ref() {
3107            if name == "SName" {
3108                if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
3109                    return s == "Error";
3110                }
3111            }
3112        }
3113    }
3114    false
3115}
3116
3117/// Reduce tact_try t goal
3118///
3119/// - Apply t to goal
3120/// - If concludes returns Error, return identity (DAxiom goal)
3121/// - Otherwise return t's result
3122fn try_tact_try_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3123    let norm_goal = normalize(ctx, goal);
3124
3125    // Apply t to goal
3126    let d_app = Term::App(Box::new(t.clone()), Box::new(norm_goal.clone()));
3127    let d = normalize(ctx, &d_app);
3128
3129    // Check if t succeeded by looking at concludes
3130    if let Some(conc) = try_concludes_reduce(ctx, &d) {
3131        if is_error_syntax(&conc) {
3132            // t failed, return identity (DAxiom goal)
3133            return Some(make_daxiom(&norm_goal));
3134        } else {
3135            // t succeeded
3136            return Some(d);
3137        }
3138    }
3139
3140    // Couldn't evaluate concludes - return identity
3141    Some(make_daxiom(&norm_goal))
3142}
3143
3144/// Reduce tact_repeat t goal
3145///
3146/// Apply t repeatedly until it fails or makes no progress.
3147/// Returns the accumulated derivation or identity if first application fails.
3148fn try_tact_repeat_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3149    const MAX_ITERATIONS: usize = 100;
3150
3151    let norm_goal = normalize(ctx, goal);
3152    let mut current_goal = norm_goal.clone();
3153    let mut last_successful_deriv: Option<Term> = None;
3154
3155    for _ in 0..MAX_ITERATIONS {
3156        // Apply t to current goal
3157        let d_app = Term::App(Box::new(t.clone()), Box::new(current_goal.clone()));
3158        let d = normalize(ctx, &d_app);
3159
3160        // Check result
3161        if let Some(conc) = try_concludes_reduce(ctx, &d) {
3162            if is_error_syntax(&conc) {
3163                // Tactic failed - stop and return what we have
3164                break;
3165            }
3166
3167            // Check for no-progress (fixed point)
3168            if syntax_equal(&conc, &current_goal) {
3169                // No progress made - stop
3170                break;
3171            }
3172
3173            // Progress made - continue
3174            current_goal = conc;
3175            last_successful_deriv = Some(d);
3176        } else {
3177            // Couldn't evaluate concludes - stop
3178            break;
3179        }
3180    }
3181
3182    // Return final derivation or identity
3183    last_successful_deriv.or_else(|| Some(make_daxiom(&norm_goal)))
3184}
3185
3186/// Reduce tact_then t1 t2 goal
3187///
3188/// - Apply t1 to goal
3189/// - If t1 fails, return Error
3190/// - Otherwise apply t2 to the result of t1
3191fn try_tact_then_reduce(
3192    ctx: &Context,
3193    t1: &Term,
3194    t2: &Term,
3195    goal: &Term,
3196) -> Option<Term> {
3197    let norm_goal = normalize(ctx, goal);
3198
3199    // Apply t1 to goal
3200    let d1_app = Term::App(Box::new(t1.clone()), Box::new(norm_goal.clone()));
3201    let d1 = normalize(ctx, &d1_app);
3202
3203    // Check if t1 succeeded
3204    if let Some(conc1) = try_concludes_reduce(ctx, &d1) {
3205        if is_error_syntax(&conc1) {
3206            // t1 failed
3207            return Some(make_error_derivation());
3208        }
3209
3210        // t1 succeeded - apply t2 to the new goal (conc1)
3211        let d2_app = Term::App(Box::new(t2.clone()), Box::new(conc1));
3212        let d2 = normalize(ctx, &d2_app);
3213
3214        // The result is d2 (which may succeed or fail)
3215        return Some(d2);
3216    }
3217
3218    // Couldn't evaluate concludes - return error
3219    Some(make_error_derivation())
3220}
3221
3222/// Reduce tact_first tactics goal
3223///
3224/// Try each tactic in the list until one succeeds.
3225/// Returns Error if all fail or list is empty.
3226fn try_tact_first_reduce(ctx: &Context, tactics: &Term, goal: &Term) -> Option<Term> {
3227    let norm_goal = normalize(ctx, goal);
3228
3229    // Extract tactics from TList
3230    let tactic_vec = extract_tlist(tactics)?;
3231
3232    for tactic in tactic_vec {
3233        // Apply this tactic to goal
3234        let d_app = Term::App(Box::new(tactic), Box::new(norm_goal.clone()));
3235        let d = normalize(ctx, &d_app);
3236
3237        // Check if it succeeded
3238        if let Some(conc) = try_concludes_reduce(ctx, &d) {
3239            if !is_error_syntax(&conc) {
3240                // Success!
3241                return Some(d);
3242            }
3243            // Failed - try next
3244        }
3245    }
3246
3247    // All failed
3248    Some(make_error_derivation())
3249}
3250
3251/// Extract elements from a TList term
3252///
3253/// TList is polymorphic: TNil A and TCons A h t
3254/// So the structure is:
3255/// - TNil A = App(Global("TNil"), type)
3256/// - TCons A h t = App(App(App(Global("TCons"), type), head), tail)
3257fn extract_tlist(term: &Term) -> Option<Vec<Term>> {
3258    let mut result = Vec::new();
3259    let mut current = term.clone();
3260
3261    loop {
3262        match &current {
3263            // TNil A = App(Global("TNil"), type)
3264            Term::App(tnil, _type) => {
3265                if let Term::Global(name) = tnil.as_ref() {
3266                    if name == "TNil" {
3267                        // Empty list
3268                        break;
3269                    }
3270                }
3271                // Try TCons A h t = App(App(App(Global("TCons"), type), head), tail)
3272                if let Term::App(partial2, tail) = &current {
3273                    if let Term::App(partial1, head) = partial2.as_ref() {
3274                        if let Term::App(tcons, _type) = partial1.as_ref() {
3275                            if let Term::Global(name) = tcons.as_ref() {
3276                                if name == "TCons" {
3277                                    result.push(head.as_ref().clone());
3278                                    current = tail.as_ref().clone();
3279                                    continue;
3280                                }
3281                            }
3282                        }
3283                    }
3284                }
3285                // Not a valid TList structure
3286                return None;
3287            }
3288            // Bare Global("TNil") without type argument - also valid
3289            Term::Global(name) if name == "TNil" => {
3290                break;
3291            }
3292            _ => {
3293                // Not a valid TList
3294                return None;
3295            }
3296        }
3297    }
3298
3299    Some(result)
3300}
3301
3302/// Reduce tact_solve t goal
3303///
3304/// - Apply t to goal
3305/// - If t fails (Error), return Error
3306/// - If t succeeds, return its result
3307fn try_tact_solve_reduce(ctx: &Context, t: &Term, goal: &Term) -> Option<Term> {
3308    let norm_goal = normalize(ctx, goal);
3309
3310    // Apply t to goal
3311    let d_app = Term::App(Box::new(t.clone()), Box::new(norm_goal.clone()));
3312    let d = normalize(ctx, &d_app);
3313
3314    // Check if t succeeded
3315    if let Some(conc) = try_concludes_reduce(ctx, &d) {
3316        if is_error_syntax(&conc) {
3317            // t failed
3318            return Some(make_error_derivation());
3319        }
3320        // t succeeded - return its derivation
3321        return Some(d);
3322    }
3323
3324    // Couldn't evaluate concludes - return error
3325    Some(make_error_derivation())
3326}
3327
3328// -------------------------------------------------------------------------
3329// Congruence Closure
3330// -------------------------------------------------------------------------
3331
3332/// Validate DCong proof by congruence.
3333///
3334/// DCong context eq_proof where:
3335/// - context is SLam param_type body
3336/// - eq_proof proves Eq T a b
3337/// Returns: Eq param_type (body[0:=a]) (body[0:=b])
3338fn try_dcong_conclude(ctx: &Context, context: &Term, eq_proof: &Term) -> Option<Term> {
3339    // Get the conclusion of the equality proof
3340    let eq_conc = try_concludes_reduce(ctx, eq_proof)?;
3341
3342    // Extract T, a, b from Eq T a b
3343    let parts = extract_eq_syntax_parts(&eq_conc);
3344    if parts.is_none() {
3345        // Not an equality proof
3346        return Some(make_sname_error());
3347    }
3348    let (_type_term, lhs, rhs) = parts.unwrap();
3349
3350    // Normalize context and check it's a lambda
3351    let norm_context = normalize(ctx, context);
3352
3353    // Extract (param_type, body) from SLam param_type body
3354    let slam_parts = extract_slam_parts(&norm_context);
3355    if slam_parts.is_none() {
3356        // Not a lambda context
3357        return Some(make_sname_error());
3358    }
3359    let (param_type, body) = slam_parts.unwrap();
3360
3361    // Substitute lhs and rhs into body at index 0
3362    let fa = try_syn_subst_reduce(ctx, &lhs, 0, &body)?;
3363    let fb = try_syn_subst_reduce(ctx, &rhs, 0, &body)?;
3364
3365    // Build result: Eq param_type fa fb
3366    Some(make_eq_syntax_three(&param_type, &fa, &fb))
3367}
3368
3369/// Extract (param_type, body) from SLam param_type body
3370///
3371/// Pattern: App(App(Global("SLam"), param_type), body)
3372fn extract_slam_parts(term: &Term) -> Option<(Term, Term)> {
3373    if let Term::App(inner, body) = term {
3374        if let Term::App(slam_ctor, param_type) = inner.as_ref() {
3375            if let Term::Global(name) = slam_ctor.as_ref() {
3376                if name == "SLam" {
3377                    return Some((param_type.as_ref().clone(), body.as_ref().clone()));
3378                }
3379            }
3380        }
3381    }
3382    None
3383}
3384
3385/// Build SApp (SApp (SApp (SName "Eq") type_s) a) b
3386///
3387/// Constructs the Syntax representation of (Eq type_s a b)
3388fn make_eq_syntax_three(type_s: &Term, a: &Term, b: &Term) -> Term {
3389    let eq_name = Term::App(
3390        Box::new(Term::Global("SName".to_string())),
3391        Box::new(Term::Lit(Literal::Text("Eq".to_string()))),
3392    );
3393
3394    // SApp (SName "Eq") type_s
3395    let app1 = Term::App(
3396        Box::new(Term::App(
3397            Box::new(Term::Global("SApp".to_string())),
3398            Box::new(eq_name),
3399        )),
3400        Box::new(type_s.clone()),
3401    );
3402
3403    // SApp (SApp (SName "Eq") type_s) a
3404    let app2 = Term::App(
3405        Box::new(Term::App(
3406            Box::new(Term::Global("SApp".to_string())),
3407            Box::new(app1),
3408        )),
3409        Box::new(a.clone()),
3410    );
3411
3412    // SApp (SApp (SApp (SName "Eq") type_s) a) b
3413    Term::App(
3414        Box::new(Term::App(
3415            Box::new(Term::Global("SApp".to_string())),
3416            Box::new(app2),
3417        )),
3418        Box::new(b.clone()),
3419    )
3420}
3421
3422// -------------------------------------------------------------------------
3423// Generic Elimination
3424// -------------------------------------------------------------------------
3425
3426/// DElim reduction with verification.
3427///
3428/// DElim ind_type motive cases → Forall ind_type motive (if verified)
3429///
3430/// Verification:
3431/// 1. Extract inductive name from ind_type Syntax
3432/// 2. Look up constructors for that inductive
3433/// 3. Extract case proofs from DCase chain
3434/// 4. Verify case count matches constructor count
3435/// 5. For each constructor, verify case conclusion matches expected
3436/// 6. Return Forall ind_type motive
3437fn try_delim_conclude(
3438    ctx: &Context,
3439    ind_type: &Term,
3440    motive: &Term,
3441    cases: &Term,
3442) -> Option<Term> {
3443    // Normalize inputs
3444    let norm_ind_type = normalize(ctx, ind_type);
3445    let norm_motive = normalize(ctx, motive);
3446    let norm_cases = normalize(ctx, cases);
3447
3448    // 1. Extract inductive name from Syntax
3449    let ind_name = match extract_inductive_name_from_syntax(&norm_ind_type) {
3450        Some(name) => name,
3451        None => return Some(make_sname_error()),
3452    };
3453
3454    // 2. Look up constructors for this inductive
3455    let constructors = ctx.get_constructors(&ind_name);
3456    if constructors.is_empty() {
3457        // Unknown inductive type
3458        return Some(make_sname_error());
3459    }
3460
3461    // 3. Extract case proofs from DCase chain
3462    let case_proofs = match extract_case_proofs(&norm_cases) {
3463        Some(proofs) => proofs,
3464        None => return Some(make_sname_error()),
3465    };
3466
3467    // 4. Verify case count matches constructor count
3468    if case_proofs.len() != constructors.len() {
3469        return Some(make_sname_error());
3470    }
3471
3472    // 5. Extract motive body (should be SLam param_type body)
3473    let motive_body = match extract_slam_body(&norm_motive) {
3474        Some(body) => body,
3475        None => return Some(make_sname_error()),
3476    };
3477
3478    // 6. For each constructor, verify case conclusion matches expected
3479    for (i, (ctor_name, _ctor_type)) in constructors.iter().enumerate() {
3480        let case_proof = &case_proofs[i];
3481
3482        // Get actual conclusion of this case proof
3483        let case_conc = match try_concludes_reduce(ctx, case_proof) {
3484            Some(c) => c,
3485            None => return Some(make_sname_error()),
3486        };
3487
3488        // Build expected conclusion based on constructor
3489        // For base case (0-ary constructor): motive[ctor/var]
3490        // For step case (recursive constructor): requires IH pattern
3491        let expected = match build_case_expected(ctx, ctor_name, &constructors, &motive_body, &norm_ind_type) {
3492            Some(e) => e,
3493            None => return Some(make_sname_error()),
3494        };
3495
3496        // Verify conclusion matches expected
3497        if !syntax_equal(&case_conc, &expected) {
3498            return Some(make_sname_error());
3499        }
3500    }
3501
3502    // 7. Return conclusion: Forall ind_type motive
3503    Some(make_forall_syntax_generic(&norm_ind_type, &norm_motive))
3504}
3505
3506/// Extract inductive name from Syntax term.
3507///
3508/// Handles:
3509/// - SName "Nat" → "Nat"
3510/// - SApp (SName "List") A → "List"
3511fn extract_inductive_name_from_syntax(term: &Term) -> Option<String> {
3512    // Case 1: SName "X"
3513    if let Term::App(sname, text) = term {
3514        if let Term::Global(ctor) = sname.as_ref() {
3515            if ctor == "SName" {
3516                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
3517                    return Some(name.clone());
3518                }
3519            }
3520        }
3521    }
3522
3523    // Case 2: SApp (SName "X") args → extract "X" from the function position
3524    if let Term::App(inner, _arg) = term {
3525        if let Term::App(sapp, func) = inner.as_ref() {
3526            if let Term::Global(ctor) = sapp.as_ref() {
3527                if ctor == "SApp" {
3528                    // Recursively extract from the function
3529                    return extract_inductive_name_from_syntax(func);
3530                }
3531            }
3532        }
3533    }
3534
3535    None
3536}
3537
3538/// Extract case proofs from DCase chain.
3539///
3540/// DCase p1 (DCase p2 DCaseEnd) → [p1, p2]
3541fn extract_case_proofs(term: &Term) -> Option<Vec<Term>> {
3542    let mut proofs = Vec::new();
3543    let mut current = term;
3544
3545    loop {
3546        // DCaseEnd - end of list
3547        if let Term::Global(name) = current {
3548            if name == "DCaseEnd" {
3549                return Some(proofs);
3550            }
3551        }
3552
3553        // DCase head tail - Pattern: App(App(DCase, head), tail)
3554        if let Term::App(inner, tail) = current {
3555            if let Term::App(dcase, head) = inner.as_ref() {
3556                if let Term::Global(name) = dcase.as_ref() {
3557                    if name == "DCase" {
3558                        proofs.push(head.as_ref().clone());
3559                        current = tail.as_ref();
3560                        continue;
3561                    }
3562                }
3563            }
3564        }
3565
3566        // Unrecognized structure
3567        return None;
3568    }
3569}
3570
3571/// Build expected case conclusion for a constructor.
3572///
3573/// For base case constructors (no recursive args): motive[ctor/var]
3574/// For recursive constructors: ∀args. IH → motive[ctor args/var]
3575fn build_case_expected(
3576    ctx: &Context,
3577    ctor_name: &str,
3578    _constructors: &[(&str, &Term)],
3579    motive_body: &Term,
3580    ind_type: &Term,
3581) -> Option<Term> {
3582    // Extract inductive name to determine constructor patterns
3583    let ind_name = extract_inductive_name_from_syntax(ind_type)?;
3584
3585    // Special case for Nat - we know its structure
3586    if ind_name == "Nat" {
3587        if ctor_name == "Zero" {
3588            // Base case: motive[Zero/var]
3589            let zero = make_sname("Zero");
3590            return try_syn_subst_reduce(ctx, &zero, 0, motive_body);
3591        } else if ctor_name == "Succ" {
3592            // Step case: ∀k:Nat. P(k) → P(Succ k)
3593            // Use the same logic as DInduction
3594            return build_induction_step_formula(ctx, motive_body);
3595        }
3596    }
3597
3598    // For other inductives, use heuristic based on constructor type
3599    // Build the constructor as Syntax: SName "CtorName"
3600    let ctor_syntax = make_sname(ctor_name);
3601
3602    // For polymorphic types, we need to apply the type argument to the constructor
3603    // e.g., for List A, Nil becomes (SApp (SName "Nil") A)
3604    let ctor_applied = apply_type_args_to_ctor(&ctor_syntax, ind_type);
3605
3606    // Get constructor type from context to determine if it's recursive
3607    if let Some(ctor_ty) = ctx.get_global(ctor_name) {
3608        // Check if constructor type contains the inductive type (recursive)
3609        if is_recursive_constructor(ctx, ctor_ty, &ind_name, ind_type) {
3610            // For recursive constructors, build the IH pattern
3611            return build_recursive_case_formula(ctx, ctor_name, ctor_ty, motive_body, ind_type, &ind_name);
3612        }
3613    }
3614
3615    // Simple base case: substitute ctor into motive body
3616    try_syn_subst_reduce(ctx, &ctor_applied, 0, motive_body)
3617}
3618
3619/// Apply type arguments from ind_type to a constructor.
3620///
3621/// If ind_type = SApp (SName "List") A, and ctor = SName "Nil",
3622/// result = SApp (SName "Nil") A
3623fn apply_type_args_to_ctor(ctor: &Term, ind_type: &Term) -> Term {
3624    // Extract type arguments from ind_type
3625    let args = extract_type_args(ind_type);
3626
3627    if args.is_empty() {
3628        return ctor.clone();
3629    }
3630
3631    // Apply each arg: SApp (... (SApp ctor arg1) ...) argN
3632    args.iter().fold(ctor.clone(), |acc, arg| {
3633        Term::App(
3634            Box::new(Term::App(
3635                Box::new(Term::Global("SApp".to_string())),
3636                Box::new(acc),
3637            )),
3638            Box::new(arg.clone()),
3639        )
3640    })
3641}
3642
3643/// Extract type arguments from polymorphic Syntax.
3644///
3645/// SApp (SApp (SName "Either") A) B → [A, B]
3646/// SApp (SName "List") A → [A]
3647/// SName "Nat" → []
3648fn extract_type_args(term: &Term) -> Vec<Term> {
3649    let mut args = Vec::new();
3650    let mut current = term;
3651
3652    // Traverse SApp chain from outside in
3653    loop {
3654        if let Term::App(inner, arg) = current {
3655            if let Term::App(sapp, func) = inner.as_ref() {
3656                if let Term::Global(ctor) = sapp.as_ref() {
3657                    if ctor == "SApp" {
3658                        args.push(arg.as_ref().clone());
3659                        current = func.as_ref();
3660                        continue;
3661                    }
3662                }
3663            }
3664        }
3665        break;
3666    }
3667
3668    // Reverse because we collected outside-in but want inside-out
3669    args.reverse();
3670    args
3671}
3672
3673/// Build Forall Syntax for generic inductive type.
3674///
3675/// Forall ind_type motive = SApp (SApp (SName "Forall") ind_type) motive
3676fn make_forall_syntax_generic(ind_type: &Term, motive: &Term) -> Term {
3677    // SApp (SName "Forall") ind_type
3678    let forall_type = Term::App(
3679        Box::new(Term::App(
3680            Box::new(Term::Global("SApp".to_string())),
3681            Box::new(make_sname("Forall")),
3682        )),
3683        Box::new(ind_type.clone()),
3684    );
3685
3686    // SApp forall_type motive
3687    Term::App(
3688        Box::new(Term::App(
3689            Box::new(Term::Global("SApp".to_string())),
3690            Box::new(forall_type),
3691        )),
3692        Box::new(motive.clone()),
3693    )
3694}
3695
3696/// Check if a constructor is recursive (has arguments of the inductive type).
3697fn is_recursive_constructor(
3698    _ctx: &Context,
3699    ctor_ty: &Term,
3700    ind_name: &str,
3701    _ind_type: &Term,
3702) -> bool {
3703    // Traverse the constructor type looking for the inductive type in argument positions
3704    // For Cons : Π(A:Type). A -> List A -> List A
3705    // The "List A" argument makes it recursive
3706
3707    fn contains_inductive(term: &Term, ind_name: &str) -> bool {
3708        match term {
3709            Term::Global(name) => name == ind_name,
3710            Term::App(f, a) => {
3711                contains_inductive(f, ind_name) || contains_inductive(a, ind_name)
3712            }
3713            Term::Pi { param_type, body_type, .. } => {
3714                contains_inductive(param_type, ind_name) || contains_inductive(body_type, ind_name)
3715            }
3716            Term::Lambda { param_type, body, .. } => {
3717                contains_inductive(param_type, ind_name) || contains_inductive(body, ind_name)
3718            }
3719            _ => false,
3720        }
3721    }
3722
3723    // Check if any parameter type (not the final result) contains the inductive
3724    fn check_params(term: &Term, ind_name: &str) -> bool {
3725        match term {
3726            Term::Pi { param_type, body_type, .. } => {
3727                // Check if this parameter has the inductive type
3728                if contains_inductive(param_type, ind_name) {
3729                    return true;
3730                }
3731                // Check remaining parameters
3732                check_params(body_type, ind_name)
3733            }
3734            _ => false,
3735        }
3736    }
3737
3738    check_params(ctor_ty, ind_name)
3739}
3740
3741/// Build the case formula for a recursive constructor.
3742///
3743/// For Cons : Π(A:Type). A -> List A -> List A
3744/// with motive P : List A -> Prop
3745/// Expected case: ∀x:A. ∀xs:List A. P(xs) -> P(Cons A x xs)
3746fn build_recursive_case_formula(
3747    ctx: &Context,
3748    ctor_name: &str,
3749    ctor_ty: &Term,
3750    motive_body: &Term,
3751    ind_type: &Term,
3752    ind_name: &str,
3753) -> Option<Term> {
3754    // Extract type args from ind_type for matching
3755    let type_args = extract_type_args(ind_type);
3756
3757    // Collect constructor arguments (skipping type parameters)
3758    let args = collect_ctor_args(ctor_ty, ind_name, &type_args);
3759
3760    if args.is_empty() {
3761        // No non-type arguments, treat as base case
3762        let ctor_applied = apply_type_args_to_ctor(&make_sname(ctor_name), ind_type);
3763        return try_syn_subst_reduce(ctx, &ctor_applied, 0, motive_body);
3764    }
3765
3766    // Build from inside out:
3767    // 1. Build ctor application: Cons A x xs (with de Bruijn indices for args)
3768    // 2. Build P(ctor args): motive_body[ctor args/var]
3769    // 3. For each recursive arg, wrap with IH: P(xs) ->
3770    // 4. For each arg, wrap with forall: ∀xs:List A.
3771
3772    // Build constructor application with de Bruijn indices
3773    let mut ctor_app = apply_type_args_to_ctor(&make_sname(ctor_name), ind_type);
3774    for (i, _) in args.iter().enumerate() {
3775        // Index from end: last arg is index 0, second-to-last is 1, etc.
3776        let idx = (args.len() - 1 - i) as i64;
3777        let var = Term::App(
3778            Box::new(Term::Global("SVar".to_string())),
3779            Box::new(Term::Lit(Literal::Int(idx))),
3780        );
3781        ctor_app = Term::App(
3782            Box::new(Term::App(
3783                Box::new(Term::Global("SApp".to_string())),
3784                Box::new(ctor_app),
3785            )),
3786            Box::new(var),
3787        );
3788    }
3789
3790    // P(ctor args) - substitute ctor_app into motive
3791    let p_ctor = try_syn_subst_reduce(ctx, &ctor_app, 0, motive_body)?;
3792
3793    // Build implications from inside out (for recursive args)
3794    let mut body = p_ctor;
3795    for (i, (arg_ty, is_recursive)) in args.iter().enumerate().rev() {
3796        if *is_recursive {
3797            // Add IH: P(arg) -> body
3798            // arg is at index (args.len() - 1 - i)
3799            let idx = (args.len() - 1 - i) as i64;
3800            let var = Term::App(
3801                Box::new(Term::Global("SVar".to_string())),
3802                Box::new(Term::Lit(Literal::Int(idx))),
3803            );
3804            let p_arg = try_syn_subst_reduce(ctx, &var, 0, motive_body)?;
3805            body = make_implies_syntax(&p_arg, &body);
3806        }
3807        // Skip non-recursive args in the implication chain
3808        let _ = (i, arg_ty); // suppress unused warning
3809    }
3810
3811    // Wrap with foralls from inside out
3812    for (arg_ty, _) in args.iter().rev() {
3813        // SLam arg_ty body
3814        let slam = Term::App(
3815            Box::new(Term::App(
3816                Box::new(Term::Global("SLam".to_string())),
3817                Box::new(arg_ty.clone()),
3818            )),
3819            Box::new(body.clone()),
3820        );
3821        // Forall arg_ty slam
3822        body = make_forall_syntax_with_type(arg_ty, &slam);
3823    }
3824
3825    Some(body)
3826}
3827
3828/// Collect constructor arguments, skipping type parameters.
3829/// Returns (arg_type, is_recursive) pairs.
3830fn collect_ctor_args(ctor_ty: &Term, ind_name: &str, type_args: &[Term]) -> Vec<(Term, bool)> {
3831    let mut args = Vec::new();
3832    let mut current = ctor_ty;
3833    let mut skip_count = type_args.len();
3834
3835    loop {
3836        match current {
3837            Term::Pi { param_type, body_type, .. } => {
3838                if skip_count > 0 {
3839                    // Skip type parameter
3840                    skip_count -= 1;
3841                } else {
3842                    // Regular argument
3843                    let is_recursive = contains_inductive_term(param_type, ind_name);
3844                    // Convert kernel type to Syntax representation
3845                    let arg_ty_syntax = kernel_type_to_syntax(param_type);
3846                    args.push((arg_ty_syntax, is_recursive));
3847                }
3848                current = body_type;
3849            }
3850            _ => break,
3851        }
3852    }
3853
3854    args
3855}
3856
3857/// Check if a kernel Term contains the inductive type.
3858fn contains_inductive_term(term: &Term, ind_name: &str) -> bool {
3859    match term {
3860        Term::Global(name) => name == ind_name,
3861        Term::App(f, a) => {
3862            contains_inductive_term(f, ind_name) || contains_inductive_term(a, ind_name)
3863        }
3864        Term::Pi { param_type, body_type, .. } => {
3865            contains_inductive_term(param_type, ind_name) || contains_inductive_term(body_type, ind_name)
3866        }
3867        Term::Lambda { param_type, body, .. } => {
3868            contains_inductive_term(param_type, ind_name) || contains_inductive_term(body, ind_name)
3869        }
3870        _ => false,
3871    }
3872}
3873
3874/// Convert a kernel Term (type) to its Syntax representation.
3875fn kernel_type_to_syntax(term: &Term) -> Term {
3876    match term {
3877        Term::Global(name) => make_sname(name),
3878        Term::Var(name) => make_sname(name), // Named variable
3879        Term::App(f, a) => {
3880            let f_syn = kernel_type_to_syntax(f);
3881            let a_syn = kernel_type_to_syntax(a);
3882            // SApp f_syn a_syn
3883            Term::App(
3884                Box::new(Term::App(
3885                    Box::new(Term::Global("SApp".to_string())),
3886                    Box::new(f_syn),
3887                )),
3888                Box::new(a_syn),
3889            )
3890        }
3891        Term::Pi { param, param_type, body_type } => {
3892            let pt_syn = kernel_type_to_syntax(param_type);
3893            let bt_syn = kernel_type_to_syntax(body_type);
3894            // SPi pt_syn bt_syn
3895            Term::App(
3896                Box::new(Term::App(
3897                    Box::new(Term::Global("SPi".to_string())),
3898                    Box::new(pt_syn),
3899                )),
3900                Box::new(bt_syn),
3901            )
3902        }
3903        Term::Sort(univ) => {
3904            // SSort univ
3905            Term::App(
3906                Box::new(Term::Global("SSort".to_string())),
3907                Box::new(univ_to_syntax(univ)),
3908            )
3909        }
3910        Term::Lit(lit) => {
3911            // SLit lit
3912            Term::App(
3913                Box::new(Term::Global("SLit".to_string())),
3914                Box::new(Term::Lit(lit.clone())),
3915            )
3916        }
3917        _ => {
3918            // Fallback for complex terms
3919            make_sname("Unknown")
3920        }
3921    }
3922}
3923
3924/// Convert a Universe to Syntax.
3925fn univ_to_syntax(univ: &crate::term::Universe) -> Term {
3926    match univ {
3927        crate::term::Universe::Prop => Term::Global("UProp".to_string()),
3928        crate::term::Universe::Type(n) => Term::App(
3929            Box::new(Term::Global("UType".to_string())),
3930            Box::new(Term::Lit(Literal::Int(*n as i64))),
3931        ),
3932    }
3933}
3934
3935// -------------------------------------------------------------------------
3936// Inversion Tactic
3937// -------------------------------------------------------------------------
3938
3939/// Inversion tactic: analyze hypothesis to derive False if no constructor matches.
3940///
3941/// Given hypothesis H of form `SApp (SName "IndName") args`, check if any constructor
3942/// of IndName can produce those args. If no constructor can match, return DInversion H.
3943fn try_try_inversion_reduce(ctx: &Context, goal: &Term) -> Option<Term> {
3944    // Extract inductive name and arguments from the hypothesis type
3945    let (ind_name, hyp_args) = match extract_applied_inductive_from_syntax(goal) {
3946        Some((name, args)) => (name, args),
3947        None => return Some(make_error_derivation()),
3948    };
3949
3950    // Check if the inductive type actually exists
3951    if !ctx.is_inductive(&ind_name) {
3952        // Unknown inductive type - cannot derive anything
3953        return Some(make_error_derivation());
3954    }
3955
3956    // Get constructors for this inductive
3957    let constructors = ctx.get_constructors(&ind_name);
3958
3959    // Check each constructor to see if it can match
3960    let mut any_possible = false;
3961    for (_ctor_name, ctor_type) in constructors.iter() {
3962        if can_constructor_match_args(ctx, ctor_type, &hyp_args, &ind_name) {
3963            any_possible = true;
3964            break;
3965        }
3966    }
3967
3968    if any_possible {
3969        // Cannot derive False - some constructor could match
3970        return Some(make_error_derivation());
3971    }
3972
3973    // All constructors impossible → build DInversion
3974    Some(Term::App(
3975        Box::new(Term::Global("DInversion".to_string())),
3976        Box::new(goal.clone()),
3977    ))
3978}
3979
3980/// Verify DInversion proof: check that no constructor can match the hypothesis.
3981fn try_dinversion_conclude(ctx: &Context, hyp_type: &Term) -> Option<Term> {
3982    let norm_hyp = normalize(ctx, hyp_type);
3983
3984    let (ind_name, hyp_args) = match extract_applied_inductive_from_syntax(&norm_hyp) {
3985        Some((name, args)) => (name, args),
3986        None => return Some(make_sname_error()),
3987    };
3988
3989    // Check if the inductive type actually exists
3990    if !ctx.is_inductive(&ind_name) {
3991        return Some(make_sname_error());
3992    }
3993
3994    let constructors = ctx.get_constructors(&ind_name);
3995
3996    // Verify ALL constructors are impossible
3997    for (_ctor_name, ctor_type) in constructors.iter() {
3998        if can_constructor_match_args(ctx, ctor_type, &hyp_args, &ind_name) {
3999            return Some(make_sname_error());
4000        }
4001    }
4002
4003    // All impossible → concludes False
4004    Some(make_sname("False"))
4005}
4006
4007/// Extract inductive name and arguments from Syntax.
4008///
4009/// SApp (SApp (SName "Even") x) y → ("Even", [x, y])
4010/// SName "False" → ("False", [])
4011fn extract_applied_inductive_from_syntax(term: &Term) -> Option<(String, Vec<Term>)> {
4012    // Base case: SName "X"
4013    if let Term::App(ctor, text) = term {
4014        if let Term::Global(ctor_name) = ctor.as_ref() {
4015            if ctor_name == "SName" {
4016                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
4017                    return Some((name.clone(), vec![]));
4018                }
4019            }
4020        }
4021    }
4022
4023    // Recursive case: SApp f x
4024    if let Term::App(inner, arg) = term {
4025        if let Term::App(sapp_ctor, func) = inner.as_ref() {
4026            if let Term::Global(ctor_name) = sapp_ctor.as_ref() {
4027                if ctor_name == "SApp" {
4028                    // Recursively extract from the function
4029                    let (name, mut args) = extract_applied_inductive_from_syntax(func)?;
4030                    args.push(arg.as_ref().clone());
4031                    return Some((name, args));
4032                }
4033            }
4034        }
4035    }
4036
4037    None
4038}
4039
4040/// Check if a constructor can possibly match the given arguments.
4041///
4042/// For constructor `even_succ : ∀n. Even n → Even (Succ (Succ n))`:
4043/// - The constructor's result pattern is `Even (Succ (Succ n))`
4044/// - If hyp_args is `[Succ (Succ (Succ Zero))]` (representing 3):
4045///   - Unify `Succ (Succ n)` with `Succ (Succ (Succ Zero))`
4046///   - This succeeds with n = Succ Zero = 1
4047///   - But then we need to check if `Even 1` is constructible (recursive)
4048fn can_constructor_match_args(
4049    ctx: &Context,
4050    ctor_type: &Term,
4051    hyp_args: &[Term],
4052    ind_name: &str,
4053) -> bool {
4054    // Decompose constructor type to get result pattern and bound variable names
4055    let (result, pattern_vars) = decompose_ctor_type_with_vars(ctor_type);
4056
4057    // Extract result's arguments (what the constructor produces)
4058    let result_args = match extract_applied_inductive_from_syntax(&kernel_type_to_syntax(&result)) {
4059        Some((name, args)) if name == *ind_name => args,
4060        _ => return false,
4061    };
4062
4063    // If argument counts don't match, can't unify
4064    if result_args.len() != hyp_args.len() {
4065        return false;
4066    }
4067
4068    // Try syntactic unification of all arguments together (tracking bindings across args)
4069    let mut bindings: std::collections::HashMap<String, Term> = std::collections::HashMap::new();
4070
4071    for (pattern, concrete) in result_args.iter().zip(hyp_args.iter()) {
4072        if !can_unify_syntax_terms_with_bindings(ctx, pattern, concrete, &pattern_vars, &mut bindings) {
4073            return false;
4074        }
4075    }
4076
4077    // If we get here, the constructor could match
4078    // (We don't check recursive hypotheses for simplicity - that would require
4079    // full inversion with backtracking)
4080    true
4081}
4082
4083/// Decompose a constructor type to get the result type and bound variable names.
4084///
4085/// `∀n:Nat. Even n → Even (Succ (Succ n))` → (`Even (Succ (Succ n))`, ["n"])
4086/// `∀A:Type. ∀x:A. Eq A x x` → (`Eq A x x`, ["A", "x"])
4087/// `Bool` → (`Bool`, [])
4088fn decompose_ctor_type_with_vars(ty: &Term) -> (Term, Vec<String>) {
4089    let mut vars = Vec::new();
4090    let mut current = ty;
4091    loop {
4092        match current {
4093            Term::Pi { param, body_type, .. } => {
4094                vars.push(param.clone());
4095                current = body_type;
4096            }
4097            _ => break,
4098        }
4099    }
4100    (current.clone(), vars)
4101}
4102
4103/// Check if two Syntax terms can unify, tracking variable bindings.
4104///
4105/// Pattern variables (names in `pattern_vars`) can bind to any concrete value,
4106/// but must bind consistently (same variable must bind to same value).
4107/// Other SNames must match exactly.
4108/// SApp recurses on function and argument.
4109fn can_unify_syntax_terms_with_bindings(
4110    ctx: &Context,
4111    pattern: &Term,
4112    concrete: &Term,
4113    pattern_vars: &[String],
4114    bindings: &mut std::collections::HashMap<String, Term>,
4115) -> bool {
4116    // SVar can match anything (explicit unification variable)
4117    if let Term::App(ctor, _idx) = pattern {
4118        if let Term::Global(name) = ctor.as_ref() {
4119            if name == "SVar" {
4120                return true;
4121            }
4122        }
4123    }
4124
4125    // SName: check if it's a pattern variable or a constant
4126    if let Term::App(ctor1, text1) = pattern {
4127        if let Term::Global(n1) = ctor1.as_ref() {
4128            if n1 == "SName" {
4129                if let Term::Lit(Literal::Text(var_name)) = text1.as_ref() {
4130                    // Check if this is a pattern variable
4131                    if pattern_vars.contains(var_name) {
4132                        // Pattern variable: check existing binding or create new one
4133                        if let Some(existing) = bindings.get(var_name) {
4134                            // Already bound: concrete must match existing binding
4135                            return syntax_terms_equal(existing, concrete);
4136                        } else {
4137                            // Not yet bound: bind to concrete value
4138                            bindings.insert(var_name.clone(), concrete.clone());
4139                            return true;
4140                        }
4141                    }
4142                }
4143                // Not a pattern variable: must match exactly
4144                if let Term::App(ctor2, text2) = concrete {
4145                    if let Term::Global(n2) = ctor2.as_ref() {
4146                        if n2 == "SName" {
4147                            return text1 == text2;
4148                        }
4149                    }
4150                }
4151                return false;
4152            }
4153        }
4154    }
4155
4156    // SApp: recurse on both function and argument
4157    if let (Term::App(inner1, arg1), Term::App(inner2, arg2)) = (pattern, concrete) {
4158        if let (Term::App(sapp1, func1), Term::App(sapp2, func2)) =
4159            (inner1.as_ref(), inner2.as_ref())
4160        {
4161            if let (Term::Global(n1), Term::Global(n2)) = (sapp1.as_ref(), sapp2.as_ref()) {
4162                if n1 == "SApp" && n2 == "SApp" {
4163                    return can_unify_syntax_terms_with_bindings(ctx, func1, func2, pattern_vars, bindings)
4164                        && can_unify_syntax_terms_with_bindings(ctx, arg1.as_ref(), arg2.as_ref(), pattern_vars, bindings);
4165                }
4166            }
4167        }
4168    }
4169
4170    // SLit: compare literal values
4171    if let (Term::App(ctor1, lit1), Term::App(ctor2, lit2)) = (pattern, concrete) {
4172        if let (Term::Global(n1), Term::Global(n2)) = (ctor1.as_ref(), ctor2.as_ref()) {
4173            if n1 == "SLit" && n2 == "SLit" {
4174                return lit1 == lit2;
4175            }
4176        }
4177    }
4178
4179    // Fall back to exact structural equality
4180    pattern == concrete
4181}
4182
4183/// Check if two Syntax terms are structurally equal.
4184fn syntax_terms_equal(a: &Term, b: &Term) -> bool {
4185    match (a, b) {
4186        (Term::App(f1, x1), Term::App(f2, x2)) => {
4187            syntax_terms_equal(f1, f2) && syntax_terms_equal(x1, x2)
4188        }
4189        (Term::Global(n1), Term::Global(n2)) => n1 == n2,
4190        (Term::Lit(l1), Term::Lit(l2)) => l1 == l2,
4191        _ => a == b,
4192    }
4193}
4194
4195// -------------------------------------------------------------------------
4196// Operator Tactics (rewrite, destruct, apply)
4197// -------------------------------------------------------------------------
4198
4199/// Extract Eq A x y components from a Syntax term.
4200///
4201/// SApp (SApp (SApp (SName "Eq") A) x) y → Some((A, x, y))
4202fn extract_eq_components_from_syntax(term: &Term) -> Option<(Term, Term, Term)> {
4203    // term = SApp (SApp (SApp (SName "Eq") A) x) y
4204    // In kernel representation: App(App(SApp, App(App(SApp, App(App(SApp, SName "Eq"), A)), x)), y)
4205
4206    // Peel off outermost SApp to get ((SApp (SApp (SName "Eq") A) x), y)
4207    let (eq_a_x, y) = extract_sapp(term)?;
4208
4209    // Peel off next SApp to get ((SApp (SName "Eq") A), x)
4210    let (eq_a, x) = extract_sapp(&eq_a_x)?;
4211
4212    // Peel off next SApp to get ((SName "Eq"), A)
4213    let (eq, a) = extract_sapp(&eq_a)?;
4214
4215    // Verify it's SName "Eq"
4216    let eq_name = extract_sname(&eq)?;
4217    if eq_name != "Eq" {
4218        return None;
4219    }
4220
4221    Some((a, x, y))
4222}
4223
4224/// Extract (f, x) from SApp f x (in kernel representation).
4225fn extract_sapp(term: &Term) -> Option<(Term, Term)> {
4226    // SApp f x = App(App(Global("SApp"), f), x)
4227    if let Term::App(inner, x) = term {
4228        if let Term::App(sapp_ctor, f) = inner.as_ref() {
4229            if let Term::Global(ctor_name) = sapp_ctor.as_ref() {
4230                if ctor_name == "SApp" {
4231                    return Some((f.as_ref().clone(), x.as_ref().clone()));
4232                }
4233            }
4234        }
4235    }
4236    None
4237}
4238
4239/// Extract name from SName "name".
4240fn extract_sname(term: &Term) -> Option<String> {
4241    if let Term::App(ctor, text) = term {
4242        if let Term::Global(ctor_name) = ctor.as_ref() {
4243            if ctor_name == "SName" {
4244                if let Term::Lit(Literal::Text(name)) = text.as_ref() {
4245                    return Some(name.clone());
4246                }
4247            }
4248        }
4249    }
4250    None
4251}
4252
4253/// Check if a Syntax term contains a specific subterm.
4254fn contains_subterm_syntax(term: &Term, target: &Term) -> bool {
4255    if syntax_equal(term, target) {
4256        return true;
4257    }
4258
4259    // Check SApp f x
4260    if let Some((f, x)) = extract_sapp(term) {
4261        if contains_subterm_syntax(&f, target) || contains_subterm_syntax(&x, target) {
4262            return true;
4263        }
4264    }
4265
4266    // Check SLam T body
4267    if let Some((t, body)) = extract_slam(term) {
4268        if contains_subterm_syntax(&t, target) || contains_subterm_syntax(&body, target) {
4269            return true;
4270        }
4271    }
4272
4273    // Check SPi T body
4274    if let Some((t, body)) = extract_spi(term) {
4275        if contains_subterm_syntax(&t, target) || contains_subterm_syntax(&body, target) {
4276            return true;
4277        }
4278    }
4279
4280    false
4281}
4282
4283/// Extract (T, body) from SLam T body.
4284fn extract_slam(term: &Term) -> Option<(Term, Term)> {
4285    if let Term::App(inner, body) = term {
4286        if let Term::App(slam_ctor, t) = inner.as_ref() {
4287            if let Term::Global(ctor_name) = slam_ctor.as_ref() {
4288                if ctor_name == "SLam" {
4289                    return Some((t.as_ref().clone(), body.as_ref().clone()));
4290                }
4291            }
4292        }
4293    }
4294    None
4295}
4296
4297/// Extract (T, body) from SPi T body.
4298fn extract_spi(term: &Term) -> Option<(Term, Term)> {
4299    if let Term::App(inner, body) = term {
4300        if let Term::App(spi_ctor, t) = inner.as_ref() {
4301            if let Term::Global(ctor_name) = spi_ctor.as_ref() {
4302                if ctor_name == "SPi" {
4303                    return Some((t.as_ref().clone(), body.as_ref().clone()));
4304                }
4305            }
4306        }
4307    }
4308    None
4309}
4310
4311/// Replace first occurrence of target with replacement in a Syntax term.
4312fn replace_first_subterm_syntax(term: &Term, target: &Term, replacement: &Term) -> Option<Term> {
4313    // If term equals target, return replacement
4314    if syntax_equal(term, target) {
4315        return Some(replacement.clone());
4316    }
4317
4318    // Try to replace in SApp f x
4319    if let Some((f, x)) = extract_sapp(term) {
4320        // First try to replace in f
4321        if let Some(new_f) = replace_first_subterm_syntax(&f, target, replacement) {
4322            return Some(make_sapp(new_f, x));
4323        }
4324        // Then try to replace in x
4325        if let Some(new_x) = replace_first_subterm_syntax(&x, target, replacement) {
4326            return Some(make_sapp(f, new_x));
4327        }
4328    }
4329
4330    // Try to replace in SLam T body
4331    if let Some((t, body)) = extract_slam(term) {
4332        if let Some(new_t) = replace_first_subterm_syntax(&t, target, replacement) {
4333            return Some(make_slam(new_t, body));
4334        }
4335        if let Some(new_body) = replace_first_subterm_syntax(&body, target, replacement) {
4336            return Some(make_slam(t, new_body));
4337        }
4338    }
4339
4340    // Try to replace in SPi T body
4341    if let Some((t, body)) = extract_spi(term) {
4342        if let Some(new_t) = replace_first_subterm_syntax(&t, target, replacement) {
4343            return Some(make_spi(new_t, body));
4344        }
4345        if let Some(new_body) = replace_first_subterm_syntax(&body, target, replacement) {
4346            return Some(make_spi(t, new_body));
4347        }
4348    }
4349
4350    // No replacement found
4351    None
4352}
4353
4354/// Rewrite tactic: given eq_proof (concluding Eq A x y) and goal,
4355/// replaces x with y (or y with x if reverse=true) in goal.
4356fn try_try_rewrite_reduce(
4357    ctx: &Context,
4358    eq_proof: &Term,
4359    goal: &Term,
4360    reverse: bool,
4361) -> Option<Term> {
4362    // Get the conclusion of eq_proof
4363    let eq_conclusion = try_concludes_reduce(ctx, eq_proof)?;
4364
4365    // Extract Eq A x y components
4366    let (ty, lhs, rhs) = match extract_eq_components_from_syntax(&eq_conclusion) {
4367        Some(components) => components,
4368        None => return Some(make_error_derivation()),
4369    };
4370
4371    // Determine what to replace based on direction
4372    let (target, replacement) = if reverse { (rhs, lhs) } else { (lhs, rhs) };
4373
4374    // Check if target exists in goal
4375    if !contains_subterm_syntax(goal, &target) {
4376        return Some(make_error_derivation());
4377    }
4378
4379    // Replace target with replacement in goal
4380    let new_goal = match replace_first_subterm_syntax(goal, &target, &replacement) {
4381        Some(ng) => ng,
4382        None => return Some(make_error_derivation()),
4383    };
4384
4385    // Build DRewrite eq_proof goal new_goal
4386    Some(Term::App(
4387        Box::new(Term::App(
4388            Box::new(Term::App(
4389                Box::new(Term::Global("DRewrite".to_string())),
4390                Box::new(eq_proof.clone()),
4391            )),
4392            Box::new(goal.clone()),
4393        )),
4394        Box::new(new_goal),
4395    ))
4396}
4397
4398/// Verify DRewrite derivation and return the new goal.
4399fn try_drewrite_conclude(
4400    ctx: &Context,
4401    eq_proof: &Term,
4402    old_goal: &Term,
4403    new_goal: &Term,
4404) -> Option<Term> {
4405    // Get the conclusion of eq_proof
4406    let eq_conclusion = try_concludes_reduce(ctx, eq_proof)?;
4407
4408    // Extract Eq A x y components
4409    let (_ty, lhs, rhs) = match extract_eq_components_from_syntax(&eq_conclusion) {
4410        Some(components) => components,
4411        None => return Some(make_sname_error()),
4412    };
4413
4414    // Verify: new_goal = old_goal[lhs := rhs] OR new_goal = old_goal[rhs := lhs]
4415    // Check forward direction first
4416    if let Some(computed_new) = replace_first_subterm_syntax(old_goal, &lhs, &rhs) {
4417        if syntax_equal(&computed_new, new_goal) {
4418            return Some(new_goal.clone());
4419        }
4420    }
4421
4422    // Check reverse direction
4423    if let Some(computed_new) = replace_first_subterm_syntax(old_goal, &rhs, &lhs) {
4424        if syntax_equal(&computed_new, new_goal) {
4425            return Some(new_goal.clone());
4426        }
4427    }
4428
4429    // Verification failed
4430    Some(make_sname_error())
4431}
4432
4433/// Destruct tactic: case analysis without induction hypotheses.
4434fn try_try_destruct_reduce(
4435    ctx: &Context,
4436    ind_type: &Term,
4437    motive: &Term,
4438    cases: &Term,
4439) -> Option<Term> {
4440    // For now, destruct is essentially the same as induction
4441    // The key difference is in what goals are expected for each case
4442    // (no IH for recursive constructors)
4443    //
4444    // We simply build a DDestruct and let verification check case proofs
4445
4446    Some(Term::App(
4447        Box::new(Term::App(
4448            Box::new(Term::App(
4449                Box::new(Term::Global("DDestruct".to_string())),
4450                Box::new(ind_type.clone()),
4451            )),
4452            Box::new(motive.clone()),
4453        )),
4454        Box::new(cases.clone()),
4455    ))
4456}
4457
4458/// Verify DDestruct derivation.
4459fn try_ddestruct_conclude(
4460    ctx: &Context,
4461    ind_type: &Term,
4462    motive: &Term,
4463    cases: &Term,
4464) -> Option<Term> {
4465    // Similar to DElim but without verifying IH in step cases
4466    // For now, we accept the derivation and return Forall ind_type motive
4467
4468    // Extract the inductive type name
4469    let ind_name = extract_inductive_name_from_syntax(ind_type)?;
4470
4471    // Verify it's actually an inductive type
4472    if !ctx.is_inductive(&ind_name) {
4473        return Some(make_sname_error());
4474    }
4475
4476    let constructors = ctx.get_constructors(&ind_name);
4477
4478    // Extract case proofs
4479    let case_proofs = match extract_case_proofs(cases) {
4480        Some(proofs) => proofs,
4481        None => return Some(make_sname_error()),
4482    };
4483
4484    // Verify case count matches
4485    if case_proofs.len() != constructors.len() {
4486        return Some(make_sname_error());
4487    }
4488
4489    // For each case, verify the conclusion matches the expected goal (without IH)
4490    // For simplicity, we just check case count matches for now
4491    // Full verification would check each case proves P(ctor args)
4492
4493    // Build Forall ind_type motive
4494    Some(make_forall_syntax_with_type(ind_type, motive))
4495}
4496
4497/// Apply tactic: manual backward chaining.
4498fn try_try_apply_reduce(
4499    ctx: &Context,
4500    hyp_name: &Term,
4501    hyp_proof: &Term,
4502    goal: &Term,
4503) -> Option<Term> {
4504    // Get the conclusion of hyp_proof
4505    let hyp_conclusion = try_concludes_reduce(ctx, hyp_proof)?;
4506
4507    // Check if it's an implication: SPi A B where B doesn't use the bound var
4508    if let Some((antecedent, consequent)) = extract_spi(&hyp_conclusion) {
4509        // Check if consequent matches goal
4510        if syntax_equal(&consequent, goal) {
4511            // Build DApply hyp_name hyp_proof goal antecedent
4512            return Some(Term::App(
4513                Box::new(Term::App(
4514                    Box::new(Term::App(
4515                        Box::new(Term::App(
4516                            Box::new(Term::Global("DApply".to_string())),
4517                            Box::new(hyp_name.clone()),
4518                        )),
4519                        Box::new(hyp_proof.clone()),
4520                    )),
4521                    Box::new(goal.clone()),
4522                )),
4523                Box::new(antecedent),
4524            ));
4525        }
4526    }
4527
4528    // Check if it's a forall that could match
4529    if let Some(forall_body) = extract_forall_body(&hyp_conclusion) {
4530        // Try to match goal with forall body (simple syntactic check)
4531        // For now, if goal appears to be an instance of the forall body, accept it
4532        // Full implementation would do proper unification
4533
4534        // Build DApply with new goal being True (trivially satisfied)
4535        return Some(Term::App(
4536            Box::new(Term::App(
4537                Box::new(Term::App(
4538                    Box::new(Term::App(
4539                        Box::new(Term::Global("DApply".to_string())),
4540                        Box::new(hyp_name.clone()),
4541                    )),
4542                    Box::new(hyp_proof.clone()),
4543                )),
4544                Box::new(goal.clone()),
4545            )),
4546            Box::new(make_sname("True")),
4547        ));
4548    }
4549
4550    // If hypothesis directly matches goal, we're done
4551    if syntax_equal(&hyp_conclusion, goal) {
4552        return Some(Term::App(
4553            Box::new(Term::App(
4554                Box::new(Term::App(
4555                    Box::new(Term::App(
4556                        Box::new(Term::Global("DApply".to_string())),
4557                        Box::new(hyp_name.clone()),
4558                    )),
4559                    Box::new(hyp_proof.clone()),
4560                )),
4561                Box::new(goal.clone()),
4562            )),
4563            Box::new(make_sname("True")),
4564        ));
4565    }
4566
4567    // Cannot apply this hypothesis to this goal
4568    Some(make_error_derivation())
4569}
4570
4571/// Verify DApply derivation.
4572fn try_dapply_conclude(
4573    ctx: &Context,
4574    hyp_name: &Term,
4575    hyp_proof: &Term,
4576    old_goal: &Term,
4577    new_goal: &Term,
4578) -> Option<Term> {
4579    // Get the conclusion of hyp_proof
4580    let hyp_conclusion = try_concludes_reduce(ctx, hyp_proof)?;
4581
4582    // If hypothesis is an implication A -> B and old_goal is B
4583    // then new_goal should be A
4584    if let Some((antecedent, consequent)) = extract_spi(&hyp_conclusion) {
4585        if syntax_equal(&consequent, old_goal) {
4586            if syntax_equal(&antecedent, new_goal) || extract_sname(new_goal) == Some("True".to_string()) {
4587                return Some(new_goal.clone());
4588            }
4589        }
4590    }
4591
4592    // If hypothesis is a forall and goal matches instantiation
4593    if let Some(_forall_body) = extract_forall_body(&hyp_conclusion) {
4594        // For forall application, the new goal is typically True or the instantiated body
4595        if extract_sname(new_goal) == Some("True".to_string()) {
4596            return Some(new_goal.clone());
4597        }
4598    }
4599
4600    // If hypothesis directly matches old_goal
4601    if syntax_equal(&hyp_conclusion, old_goal) {
4602        if extract_sname(new_goal) == Some("True".to_string()) {
4603            return Some(new_goal.clone());
4604        }
4605    }
4606
4607    // Verification failed
4608    Some(make_sname_error())
4609}
4610