Skip to main content

logicaffeine_kernel/
reduction.rs

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