/-
  CubicalTransport.Inductive
  ==========================
  Helper combinators and canonical schema instances for the REL1
  schema-based inductive (and higher-inductive) type system.

  This module sits *on top of* `Syntax.lean` — it doesn't add new
  primitives, only ergonomic builders.  Every value here is a plain
  CTypeSchema / CType / CTerm; nothing is opaque.  Downstream
  consumers should prefer these helpers over hand-spelling
  CTypeSchema literals so the canonical encodings are maintained
  in one place.

  ## Sections

  · §1  Argument-shape helpers
  · §2  Schema constructors (mkSchema, mkCtor, mkPath)
  · §3  Type-level helpers (CType.list, CType.nat, …, CType.s1, …)
  · §4  Term-level helpers (zeroC, succC, nilC, consC, baseC, loopC, …)
  · §5  Eliminator builders (natElim, listElim, …)

  ## Design notes

  · Boundary clauses inside HIT path constructors reference *dim args*
    via `DimVar.mk "$d_k"` (zero-indexed among `.dim` args) and
    *non-dim args* via `CTerm.var "$arg_k"` (zero-indexed in the full
    arg list).  See INDUCTIVE_TYPES.md §4 for the discipline.

  · The schemas here are concrete values, not `def`s under a
    `noncomputable` veil — `Nat`, `Bool`, etc. are first-class data
    that downstream code can pattern-match on or use as parameters.
-/

import CubicalTransport.Syntax

namespace CubicalTransport.Inductive

-- ── §1. Argument-shape helpers ─────────────────────────────────────────────

/-- A non-recursive argument of a closed CType. -/
@[inline] def argType (A : CType) : CTypeArg := .type A

/-- A reference to the schema's `i`th type parameter. -/
@[inline] def argParam (i : Nat) : CTypeArg := .param i

/-- A recursive argument of the inductive being defined. -/
@[inline] def argSelf : CTypeArg := .self

/-- A dimension argument (path-constructor binder). -/
@[inline] def argDim : CTypeArg := .dim

-- ── §2. Schema constructors ────────────────────────────────────────────────

/-- A point constructor (no boundary system, empty boundary list). -/
@[inline] def mkCtor (name : String) (args : List CTypeArg) : CtorSpec :=
  .mk name args []

/-- A path constructor: a constructor whose argument list contains at
    least one `.dim` arg, and whose boundary system specifies the
    canonical reduction on the corresponding cube faces. -/
@[inline] def mkPath (name : String) (args : List CTypeArg)
    (boundary : List (FaceFormula × CTerm)) : CtorSpec :=
  .mk name args boundary

/-- Build a schema from name, parameter count, and constructor list. -/
@[inline] def mkSchema (name : String) (numParams : Nat)
    (ctors : List CtorSpec) : CTypeSchema :=
  .mk name numParams ctors

-- ── §3. Canonical plain inductive schemas ──────────────────────────────────

/-- The natural numbers as a schema.

    `Nat` has two point constructors:
    - `zero : Nat`
    - `succ : Nat → Nat` (recursive arg: `.self`)
-/
def natSchema : CTypeSchema :=
  mkSchema "Nat" 0
    [ mkCtor "zero" []
    , mkCtor "succ" [.self] ]

/-- The booleans as a schema. -/
def boolSchema : CTypeSchema :=
  mkSchema "Bool" 0
    [ mkCtor "false" []
    , mkCtor "true"  [] ]

/-- Polymorphic lists as a schema, parameterised by the element type. -/
def listSchema : CTypeSchema :=
  mkSchema "List" 1
    [ mkCtor "nil"  []
    , mkCtor "cons" [.param 0, .self] ]

-- ── §4. Canonical HIT schemas ──────────────────────────────────────────────

/-- The circle `S¹` as a HIT.

    Constructors:
    - `base : S¹`                       (point)
    - `loop : (i : I) → S¹`             (path: `loop @ 0 = base = loop @ 1`)

    Boundary: at `i=0` and `i=1`, `loop` returns `base`.

    The `loop` boundary references `base` via the schema's own
    `.ctor` constructor at the same schema (recursive self-reference
    inside the schema's body).  We construct it inline here. -/
def s1Schema : CTypeSchema :=
  -- Build a self-referential schema: `loop`'s boundary uses
  -- `.ctor s1Schema "base" [] []` which in turn references `s1Schema`.
  -- Since the recursion is structural through the schema's data
  -- (not through a Lean-level fixpoint), we define `s1Schema` via
  -- Lean's well-founded recursion through structural data; the
  -- result is a finite, well-formed `CTypeSchema` value — but Lean
  -- won't accept a literal self-recursive `def` like this.
  --
  -- Workaround: take the schema *name* + a fresh local schema that
  -- refers to itself via a placeholder.  At construction time we
  -- patch the placeholder.  This is implemented as a small fix-up
  -- below.
  let baseAt (s : CTypeSchema) : CTerm := .ctor s "base" [] []
  -- We need `s1Schema = mkSchema "S¹" 0 [base, loop]` where
  -- `loop` carries a boundary that references `s1Schema`.
  -- Lean's `let rec` doesn't handle this for `CTypeSchema` the
  -- way it would for a function — but we can use a `where`-style
  -- two-step build: first build a "stub" schema, then build the
  -- final `loop` referring to it.  Both schemas have the same
  -- structural shape, so `s1Schema` is well-defined.
  let stub : CTypeSchema :=
    mkSchema "S¹" 0
      [ mkCtor "base" []
      , mkPath "loop" [.dim] [] ]
  let d : DimVar := ⟨"$d_0"⟩
  mkSchema "S¹" 0
    [ mkCtor "base" []
    , mkPath "loop" [.dim]
        [ (.eq0 d, baseAt stub)
        , (.eq1 d, baseAt stub) ] ]

/-- The interval as a HIT.

    Constructors:
    - `zero : I`
    - `one  : I`
    - `seg  : (i : I) → I` with `seg @ 0 = zero`, `seg @ 1 = one`. -/
def intervalSchema : CTypeSchema :=
  let stub : CTypeSchema :=
    mkSchema "I" 0
      [ mkCtor "zero" []
      , mkCtor "one"  []
      , mkPath "seg" [.dim] [] ]
  let zeroAt (s : CTypeSchema) : CTerm := .ctor s "zero" [] []
  let oneAt  (s : CTypeSchema) : CTerm := .ctor s "one"  [] []
  let d : DimVar := ⟨"$d_0"⟩
  mkSchema "I" 0
    [ mkCtor "zero" []
    , mkCtor "one"  []
    , mkPath "seg" [.dim]
        [ (.eq0 d, zeroAt stub)
        , (.eq1 d, oneAt  stub) ] ]

/-- Propositional truncation `‖A‖₋₁` as a HIT.

    Constructors:
    - `inT  : A → ‖A‖₋₁`           (point)
    - `squash : (x y : ‖A‖₋₁) → (i : I) → ‖A‖₋₁`
        with `squash x y @ 0 = x`, `squash x y @ 1 = y`. -/
def propTruncSchema : CTypeSchema :=
  let d : DimVar := ⟨"$d_0"⟩
  -- arg index: 0 = first .self ("$arg_0"), 1 = second .self ("$arg_1"),
  -- 2 = .dim ($d_0).  Boundary references positional arg names.
  mkSchema "‖_‖₋₁" 1
    [ mkCtor "inT"    [.param 0]
    , mkPath "squash" [.self, .self, .dim]
        [ (.eq0 d, .var "$arg_0")
        , (.eq1 d, .var "$arg_1") ] ]

-- ── §5. Type-level helpers ─────────────────────────────────────────────────

/-- The `CType` for natural numbers. -/
@[inline] def CType.natC : CType := .ind natSchema []

/-- The `CType` for booleans. -/
@[inline] def CType.boolC : CType := .ind boolSchema []

/-- The `CType` for lists with element type `A`. -/
@[inline] def CType.listC (A : CType) : CType := .ind listSchema [A]

/-- The `CType` for the circle. -/
@[inline] def CType.s1C : CType := .ind s1Schema []

/-- The `CType` for the interval. -/
@[inline] def CType.intervalC : CType := .ind intervalSchema []

/-- The `CType` for propositional truncation `‖A‖₋₁`. -/
@[inline] def CType.propTruncC (A : CType) : CType :=
  .ind propTruncSchema [A]

-- ── §6. Term-level helpers ─────────────────────────────────────────────────

namespace CTerm

/-- The `CTerm` `zero : Nat`. -/
@[inline] def zeroC : CTerm := .ctor natSchema "zero" [] []

/-- The `CTerm` `succ n` for a given `n : Nat`. -/
@[inline] def succC (n : CTerm) : CTerm := .ctor natSchema "succ" [] [n]

/-- The `CTerm` `false : Bool`. -/
@[inline] def falseC : CTerm := .ctor boolSchema "false" [] []

/-- The `CTerm` `true : Bool`. -/
@[inline] def trueC  : CTerm := .ctor boolSchema "true"  [] []

/-- The `CTerm` `nil : List A`.  Carries the element type as parameter. -/
@[inline] def nilC (A : CType) : CTerm := .ctor listSchema "nil" [A] []

/-- The `CTerm` `cons x xs : List A`. -/
@[inline] def consC (A : CType) (x xs : CTerm) : CTerm :=
  .ctor listSchema "cons" [A] [x, xs]

/-- The `CTerm` `base : S¹`. -/
@[inline] def baseC : CTerm := .ctor s1Schema "base" [] []

/-- The `CTerm` `loop @ r : S¹` where `r : DimExpr`. -/
@[inline] def loopC (r : DimExpr) : CTerm :=
  .ctor s1Schema "loop" [] [.dimExpr r]

/-- The `CTerm` `inT a : ‖A‖₋₁`. -/
@[inline] def inTC (A : CType) (a : CTerm) : CTerm :=
  .ctor propTruncSchema "inT" [A] [a]

/-- The `CTerm` `squash x y @ r : ‖A‖₋₁`. -/
@[inline] def squashC (A : CType) (x y : CTerm) (r : DimExpr) : CTerm :=
  .ctor propTruncSchema "squash" [A] [x, y, .dimExpr r]

/-- Build a Nat literal `n` as a tower of `succ`s on `zero`. -/
def natLit : Nat → CTerm
  | 0     => zeroC
  | n + 1 => succC (natLit n)

end CTerm

-- ── §7. Eliminator builders ────────────────────────────────────────────────

/-- Build an `indElim` for `Nat`.  `motive` is the result type-former,
    `caseZero` the body for `zero`, `caseSucc` the body for `succ`
    (curried as `λ pred ih. body`).  `target` is the natural being
    eliminated. -/
def natElim (motive caseZero caseSucc target : CTerm) : CTerm :=
  .indElim natSchema [] motive
    [("zero", caseZero), ("succ", caseSucc)] target

/-- Build an `indElim` for `Bool`. -/
def boolElim (motive caseFalse caseTrue target : CTerm) : CTerm :=
  .indElim boolSchema [] motive
    [("false", caseFalse), ("true", caseTrue)] target

/-- Build an `indElim` for `List A`.  `caseCons` is curried as
    `λ head tail ih. body`. -/
def listElim (A : CType) (motive caseNil caseCons target : CTerm) : CTerm :=
  .indElim listSchema [A] motive
    [("nil", caseNil), ("cons", caseCons)] target

end CubicalTransport.Inductive
