Skip to main content

logicaffeine_kernel/
reduction.rs

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