/-
  Topolei.Cubical.Equiv
  =====================
  Half-adjoint equivalences between cubical types (cells-spec §5.8).

  `EquivData` packages a forward map, inverse, section, retraction, and a
  coherence 2-cell.  It is the parameter for `Glue` types in §5.7.

  Shape: we store the five components as raw `CTerm`s rather than as a
  Σ-type of such, because CType does not yet have a Σ constructor.  Typing
  (that `f : A → B`, `fInv : B → A`, etc.) is a per-use proof obligation;
  inhabitants such as `idEquiv` exhibit the shape directly and the boundary
  reductions fall out from substDim on term variables.

  The half-adjoint discipline follows HoTT Book §4.2:
    f   : A → B              forward map
    fInv: B → A              inverse
    sec : Π b : B. Path B (f (fInv b)) b                     ε  (section)
    ret : Π a : A. Path A (fInv (f a)) a                     η  (retraction)
    coh : Π a : A. Path (Path B (f a) (f a))
                        (ap f (ret a))
                        (sec (f a))                           τ  (coherence)

  In our non-dependent Π, the inner types depend on the outer λ's bound
  variable through `CTerm` endpoints (which can reference any term variable).

  Fiber types / contractibility of fibers are intentionally deferred: they
  need `.sigma` on `CType`, which is not yet in the syntax (STATUS.md
  "Priority order" item 3).
-/

import CubicalTransport.Typing

-- ── Equivalence data ─────────────────────────────────────────────────────────

/-- Half-adjoint equivalence between two types, represented as five `CTerm`s. -/
structure EquivData where
  /-- Forward map  `f : A → B`. -/
  f    : CTerm
  /-- Inverse map  `fInv : B → A`. -/
  fInv : CTerm
  /-- Section  `ε : Π b. Path B (f (fInv b)) b`. -/
  sec  : CTerm
  /-- Retraction  `η : Π a. Path A (fInv (f a)) a`. -/
  ret  : CTerm
  /-- Coherence 2-cell  `τ : Π a. Path (Path B (f a) (f a))
                                      (ap f (ret a))
                                      (sec (f a))`. -/
  coh  : CTerm
  deriving Repr

-- ── Named-variable hygiene constants ─────────────────────────────────────────
-- Reserved names for the identity equivalence's bound variables.
-- Chosen with `$` prefixes to avoid collision with user code, consistent
-- with the `"$y"` / `⟨"$fj"⟩` convention used by `vApp vCompFun`.

namespace EquivData

/-- Reserved term variable for the identity equivalence's forward / inverse
    bound argument.  User code using `$`-prefixed names is assumed absent. -/
def idEquivVar : String := "$x"

/-- Reserved dimension variable for the sec/ret path. -/
def idEquivDim : DimVar := ⟨"$e"⟩

/-- Reserved outer dimension variable for the coherence 2-cell. -/
def idEquivDimOuter : DimVar := ⟨"$eo"⟩

/-- Reserved inner dimension variable for the coherence 2-cell. -/
def idEquivDimInner : DimVar := ⟨"$ei"⟩

end EquivData

-- ── Identity equivalence ─────────────────────────────────────────────────────

/-- The identity equivalence on any type `A`.

    All five components are constant in their path arguments:
      f    = λx. x
      fInv = λx. x
      sec  = λx. ⟨e⟩ x           -- refl_x
      ret  = λx. ⟨e⟩ x           -- refl_x
      coh  = λx. ⟨eo⟩⟨ei⟩ x     -- refl_refl_x (constant 2-cell)

    `A` is not mentioned in the term structure; the types are implied by
    the calling context (e.g., a `HasType` derivation or a `.glue` face).
    This matches CCHM's treatment where `idEquiv` is universe-polymorphic
    and the type is inferred. -/
def idEquiv (_A : CType) : EquivData :=
  let x  := EquivData.idEquivVar
  let e  := EquivData.idEquivDim
  let eo := EquivData.idEquivDimOuter
  let ei := EquivData.idEquivDimInner
  { f    := .lam x (.var x)
    fInv := .lam x (.var x)
    sec  := .lam x (.plam e  (.var x))
    ret  := .lam x (.plam e  (.var x))
    coh  := .lam x (.plam eo (.plam ei (.var x))) }

-- ── Projection rfl-lemmas ────────────────────────────────────────────────────
-- These are not strictly necessary (the definition is transparent) but they
-- let downstream code rewrite through `idEquiv` without unfolding.

namespace idEquiv

theorem f_def (A : CType) :
    (idEquiv A).f = .lam EquivData.idEquivVar (.var EquivData.idEquivVar) := rfl

theorem fInv_def (A : CType) :
    (idEquiv A).fInv = .lam EquivData.idEquivVar (.var EquivData.idEquivVar) := rfl

theorem sec_def (A : CType) :
    (idEquiv A).sec =
      .lam EquivData.idEquivVar
        (.plam EquivData.idEquivDim (.var EquivData.idEquivVar)) := rfl

theorem ret_def (A : CType) :
    (idEquiv A).ret =
      .lam EquivData.idEquivVar
        (.plam EquivData.idEquivDim (.var EquivData.idEquivVar)) := rfl

theorem coh_def (A : CType) :
    (idEquiv A).coh =
      .lam EquivData.idEquivVar
        (.plam EquivData.idEquivDimOuter
          (.plam EquivData.idEquivDimInner (.var EquivData.idEquivVar))) := rfl

/-- `idEquiv` is constant in its type argument up to the five component terms. -/
theorem components_independent_of_A (A B : CType) :
    (idEquiv A).f = (idEquiv B).f ∧
    (idEquiv A).fInv = (idEquiv B).fInv ∧
    (idEquiv A).sec = (idEquiv B).sec ∧
    (idEquiv A).ret = (idEquiv B).ret ∧
    (idEquiv A).coh = (idEquiv B).coh :=
  ⟨rfl, rfl, rfl, rfl, rfl⟩

end idEquiv

-- ── Forward map typing for idEquiv ───────────────────────────────────────────
-- The five components type-check in the empty context.  We prove typing for
-- `f` and `fInv` directly (they are `λx. x`); `sec` and `ret` follow from
-- `HasType.plam` giving the reflexivity path at `var x` (substDim on a term
-- variable is identity, so both endpoints are `var x`).

namespace idEquiv

/-- `(idEquiv A).f : A → A`. -/
theorem hasType_f (Γ : Ctx) (A : CType) :
    HasType Γ (idEquiv A).f (.pi A A) := by
  show HasType Γ (.lam EquivData.idEquivVar (.var EquivData.idEquivVar)) (.pi A A)
  exact HasType.lam (HasType.var (by simp))

/-- `(idEquiv A).fInv : A → A`. -/
theorem hasType_fInv (Γ : Ctx) (A : CType) :
    HasType Γ (idEquiv A).fInv (.pi A A) :=
  hasType_f Γ A

/-- `(idEquiv A).sec : A → Path A x x`  (reflexivity at the bound variable).

    Under our non-dependent Π, the codomain is a fixed `CType` whose path
    endpoints reference the bound term variable `$x`.  `HasType.plam` on a
    term variable gives a path whose boundaries are both `.var x` because
    `substDim` does not descend into term variables.

    Note: this does *not* match the "full" section type
    `Path A (f (fInv x)) x` because identity-of-β-reduction is not part of
    our typing relation.  We return the propositionally-reduced type. -/
theorem hasType_sec_refl (Γ : Ctx) (A : CType) :
    HasType Γ (idEquiv A).sec
      (.pi A (.path A
        (CTerm.var EquivData.idEquivVar)
        (CTerm.var EquivData.idEquivVar))) := by
  show HasType Γ
    (.lam EquivData.idEquivVar
      (.plam EquivData.idEquivDim (.var EquivData.idEquivVar)))
    (.pi A (.path A (.var EquivData.idEquivVar) (.var EquivData.idEquivVar)))
  -- plam gives Path A (var[i:=0]) (var[i:=1]) = Path A (var x) (var x) since
  -- CTerm.substDim does not descend into term variables.
  exact HasType.lam (HasType.plam (HasType.var (by simp)))

/-- `(idEquiv A).ret : A → Path A x x`. -/
theorem hasType_ret_refl (Γ : Ctx) (A : CType) :
    HasType Γ (idEquiv A).ret
      (.pi A (.path A
        (CTerm.var EquivData.idEquivVar)
        (CTerm.var EquivData.idEquivVar))) :=
  hasType_sec_refl Γ A

/-- `(idEquiv A).coh : A → Path (Path A x x) (⟨ei⟩ x) (⟨ei⟩ x)`.

    The inner plam gives `Path A x x` (by the same argument as `sec`); the
    outer plam gives a path between two copies of `⟨ei⟩ x` — because the
    outer `substDim eo` leaves `plam ei (var x)` alone (the var is a term
    variable, unaffected by dim substitution).  This is the "constant
    2-cell at refl". -/
theorem hasType_coh_refl_refl (Γ : Ctx) (A : CType) :
    HasType Γ (idEquiv A).coh
      (.pi A
        (.path
          (.path A
            (CTerm.var EquivData.idEquivVar)
            (CTerm.var EquivData.idEquivVar))
          (CTerm.plam EquivData.idEquivDimInner
            (CTerm.var EquivData.idEquivVar))
          (CTerm.plam EquivData.idEquivDimInner
            (CTerm.var EquivData.idEquivVar)))) := by
  show HasType Γ
    (.lam EquivData.idEquivVar
      (.plam EquivData.idEquivDimOuter
        (.plam EquivData.idEquivDimInner (.var EquivData.idEquivVar))))
    _
  exact HasType.lam
    (HasType.plam
      (HasType.plam (HasType.var (by simp))))

end idEquiv
