/-
  Topolei.Cubical.DimLine
  =======================
  Lines of types — the domain of transport (Step 2 of the transport plan).

  A DimLine is a type abstracted over one dimension variable:  λi. A
  This is the argument to transpⁱ (λi. A) φ t₀.

  The two endpoints are:
    at0 = A[i := false]   — the type at i = 0
    at1 = A[i := true]    — the type at i = 1

  A constant line  const A i  packages A with binder i.
  When i does not appear free in A, at0 = at1 = A.

  The face connection (atBool_eq0_face / atBool_eq1_face) states that
  whenever eq0 i or eq1 i holds in an environment, the environment's
  value at i selects the correct endpoint type.
-/

import CubicalTransport.Subst

-- ── DimLine ───────────────────────────────────────────────────────────────────

/-- A line of types: a CType abstracted over one dimension variable. -/
structure DimLine where
  binder : DimVar
  body   : CType

-- ── Endpoint projections ──────────────────────────────────────────────────────

def DimLine.at0   (L : DimLine) : CType := L.body.substDim L.binder false
def DimLine.at1   (L : DimLine) : CType := L.body.substDim L.binder true
def DimLine.atBool (L : DimLine) (b : Bool) : CType := L.body.substDim L.binder b

-- ── atBool reduction lemmas ───────────────────────────────────────────────────

theorem DimLine.atBool_false (L : DimLine) : L.atBool false = L.at0 := rfl
theorem DimLine.atBool_true  (L : DimLine) : L.atBool true  = L.at1 := rfl

theorem DimLine.atBool_cases (L : DimLine) (b : Bool) :
    L.atBool b = if b then L.at1 else L.at0 := by cases b <;> rfl

-- ── Constant line ─────────────────────────────────────────────────────────────

def DimLine.const (A : CType) (i : DimVar) : DimLine := { binder := i, body := A }

theorem DimLine.const_at0 (A : CType) (i : DimVar) :
    (DimLine.const A i).at0 = A.substDim i false := rfl

theorem DimLine.const_at1 (A : CType) (i : DimVar) :
    (DimLine.const A i).at1 = A.substDim i true := rfl

-- ── Absent dimension ──────────────────────────────────────────────────────────
-- Syntactic predicate: i does not appear in any DimExpr within the term/type.
-- `DimExpr.dimAbsent` is defined in Interval.lean (its natural home).

mutual
  def CTerm.dimAbsent (i : DimVar) : CTerm → Bool
    | .var _         => true
    | .lam _ t       => t.dimAbsent i
    | .app f a       => f.dimAbsent i && a.dimAbsent i
    | .plam j t      => if j = i then true else t.dimAbsent i
    | .papp t r      => t.dimAbsent i && r.dimAbsent i
    -- transp/comp: A is ignored here (matching the same approximation
    -- in `CTerm.substDim`).  φ is checked via FaceFormula.dimAbsent.
    | .transp _ _ φ t   => φ.dimAbsent i && t.dimAbsent i
    | .comp   _ _ φ u t => φ.dimAbsent i && u.dimAbsent i && t.dimAbsent i
    | .compN  _ _ clauses t =>
        CTerm.dimAbsent.clauses i clauses && t.dimAbsent i
    -- Glue introduction / elimination: check face + all sub-terms.
    | .glueIn φ t a     => φ.dimAbsent i && t.dimAbsent i && a.dimAbsent i
    | .unglue φ f g     => φ.dimAbsent i && f.dimAbsent i && g.dimAbsent i
    -- Σ constructors: structural recursion into sub-terms.
    | .pair a b         => a.dimAbsent i && b.dimAbsent i
    | .fst t            => t.dimAbsent i
    | .snd t            => t.dimAbsent i

  /-- Helper: check that `i` is absent from every clause in a system. -/
  def CTerm.dimAbsent.clauses (i : DimVar) :
      List (FaceFormula × CTerm) → Bool
    | []               => true
    | (φ, u) :: rest   =>
        φ.dimAbsent i && u.dimAbsent i && CTerm.dimAbsent.clauses i rest
end

def CType.dimAbsent (i : DimVar) : CType → Bool
  | .univ       => true
  | .pi A B     => A.dimAbsent i && B.dimAbsent i
  | .path A a t => A.dimAbsent i && a.dimAbsent i && t.dimAbsent i
  | .sigma A B  => A.dimAbsent i && B.dimAbsent i
  | .glue φ T f fInv sec ret coh A =>
      φ.dimAbsent i && T.dimAbsent i &&
        f.dimAbsent i && fInv.dimAbsent i &&
        sec.dimAbsent i && ret.dimAbsent i && coh.dimAbsent i &&
        A.dimAbsent i

-- ── Absence → subst is identity: DimExpr level ───────────────────────────────

-- "sub `var i → X` in `r` leaves r unchanged when i is absent from r"
-- Note: DimExpr.subst i X r substitutes (var i → X) in r.
--       Dot notation r.subst i X inserts r as the *replacement*, not target —
--       so we write the explicit form DimExpr.subst i X r here.
theorem DimExpr.subst_of_absent (i : DimVar) (X : DimExpr) (r : DimExpr)
    (h : r.dimAbsent i = true) :
    DimExpr.subst i X r = r := by
  induction r with
  | zero => rfl
  | one  => rfl
  | var j =>
      simp only [dimAbsent, bne_iff_ne] at h
      simp only [DimExpr.subst, if_neg h]
  | inv r ih =>
      simp only [dimAbsent] at h; simp [DimExpr.subst, ih h]
  | meet r s ihr ihs =>
      simp only [dimAbsent, Bool.and_eq_true] at h
      simp [DimExpr.subst, ihr h.1, ihs h.2]
  | join r s ihr ihs =>
      simp only [dimAbsent, Bool.and_eq_true] at h
      simp [DimExpr.subst, ihr h.1, ihs h.2]

-- ── Absence → subst is identity: CTerm and CType ─────────────────────────────
-- These require simultaneous induction on the CTerm/CType mutual inductive.
-- The `induction` tactic is blocked for mutual types, so we use a `def`
-- (which Lean accepts for structural recursion via match) to build the proof.
--
-- Generalised to arbitrary `r : DimExpr` (not just Bool endpoints) — needed
-- for line reversal in transport, where `r = inv i`.  The Bool version is
-- a corollary at the canonical endpoint DimExpr.

mutual
  private def CTerm.substDim_absent_aux (i : DimVar) (r : DimExpr) :
      (t : CTerm) → CTerm.dimAbsent i t = true → CTerm.substDim i r t = t
    | .var _,    _ => rfl
    | .lam x t,  h => by
        simp only [CTerm.dimAbsent] at h
        show CTerm.lam x (t.substDim i r) = CTerm.lam x t
        congr 1; exact CTerm.substDim_absent_aux i r t h
    | .app f a,  h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        show CTerm.app (f.substDim i r) (a.substDim i r) = CTerm.app f a
        congr 1
        · exact CTerm.substDim_absent_aux i r f h.1
        · exact CTerm.substDim_absent_aux i r a h.2
    | .plam j t, h => by
        show (if j = i then CTerm.plam j t
              else CTerm.plam j (t.substDim i r)) = CTerm.plam j t
        by_cases hji : j = i
        · subst hji; simp only [ite_true]
        · simp only [if_neg hji]
          simp only [CTerm.dimAbsent, if_neg hji] at h
          exact congrArg (CTerm.plam j) (CTerm.substDim_absent_aux i r t h)
    | .papp t s, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        show CTerm.papp (t.substDim i r) (DimExpr.subst i r s) = CTerm.papp t s
        congr 1
        · exact CTerm.substDim_absent_aux i r t h.1
        · exact DimExpr.subst_of_absent i r s h.2
    | .transp j A φ t, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        have hφ := FaceFormula.substDim_of_absent i r φ h.1
        have ht := CTerm.substDim_absent_aux i r t h.2
        rw [hφ, ht]
    | .comp j A φ u t, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        obtain ⟨⟨hφ, hu⟩, ht⟩ := h
        rw [FaceFormula.substDim_of_absent i r φ hφ,
            CTerm.substDim_absent_aux i r u hu,
            CTerm.substDim_absent_aux i r t ht]
    | .compN j A clauses t, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        rw [CTerm.substDim.clauses_of_absent i r clauses h.1,
            CTerm.substDim_absent_aux i r t h.2]
    | .glueIn φ t a, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        obtain ⟨⟨hφ, ht⟩, ha⟩ := h
        rw [FaceFormula.substDim_of_absent i r φ hφ,
            CTerm.substDim_absent_aux i r t ht,
            CTerm.substDim_absent_aux i r a ha]
    | .unglue φ f g, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        obtain ⟨⟨hφ, hf⟩, hg⟩ := h
        rw [FaceFormula.substDim_of_absent i r φ hφ,
            CTerm.substDim_absent_aux i r f hf,
            CTerm.substDim_absent_aux i r g hg]
    | .pair a b, h => by
        simp only [CTerm.dimAbsent, Bool.and_eq_true] at h
        simp only [CTerm.substDim]
        rw [CTerm.substDim_absent_aux i r a h.1,
            CTerm.substDim_absent_aux i r b h.2]
    | .fst t, h => by
        simp only [CTerm.dimAbsent] at h
        simp only [CTerm.substDim]
        rw [CTerm.substDim_absent_aux i r t h]
    | .snd t, h => by
        simp only [CTerm.dimAbsent] at h
        simp only [CTerm.substDim]
        rw [CTerm.substDim_absent_aux i r t h]

  /-- Helper: `substDim.clauses` is identity on clause lists whose every
      `(face, body)` pair has `i` absent. -/
  private def CTerm.substDim.clauses_of_absent (i : DimVar) (r : DimExpr) :
      (clauses : List (FaceFormula × CTerm)) →
      CTerm.dimAbsent.clauses i clauses = true →
      CTerm.substDim.clauses i r clauses = clauses
    | [],             _ => rfl
    | (φ, u) :: rest, h => by
        simp only [CTerm.dimAbsent.clauses, Bool.and_eq_true] at h
        simp only [CTerm.substDim.clauses]
        obtain ⟨⟨hφ, hu⟩, hrest⟩ := h
        rw [FaceFormula.substDim_of_absent i r φ hφ,
            CTerm.substDim_absent_aux i r u hu,
            CTerm.substDim.clauses_of_absent i r rest hrest]
end

/-- Generalised: `CTerm.substDim i r t = t` whenever `i` is absent from `t`,
    for any DimExpr `r`. -/
theorem CTerm.substDim_of_absent (i : DimVar) (r : DimExpr) (t : CTerm)
    (h : CTerm.dimAbsent i t = true) : CTerm.substDim i r t = t :=
  CTerm.substDim_absent_aux i r t h

/-- Bool-endpoint corollary of `CTerm.substDim_of_absent`. -/
theorem CTerm.substDimBool_of_absent (i : DimVar) (b : Bool) (t : CTerm)
    (h : CTerm.dimAbsent i t = true) : CTerm.substDimBool i b t = t := by
  unfold CTerm.substDimBool
  exact CTerm.substDim_of_absent i _ t h

private def CType.substDim_absent_aux (i : DimVar) (b : Bool) :
    (A : CType) → CType.dimAbsent i A = true → CType.substDim i b A = A
  | .univ,      _ => rfl
  | .pi A B,    h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.pi (CType.substDim i b A) (CType.substDim i b B) = CType.pi A B
      congr 1
      · exact CType.substDim_absent_aux i b A h.1
      · exact CType.substDim_absent_aux i b B h.2
  | .path A a t, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.path (CType.substDim i b A)
                      (CTerm.substDimBool i b a) (CTerm.substDimBool i b t) =
           CType.path A a t
      congr 1
      · exact CType.substDim_absent_aux i b A h.1.1
      · exact CTerm.substDimBool_of_absent i b a h.1.2
      · exact CTerm.substDimBool_of_absent i b t h.2
  | .sigma A B, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.sigma (CType.substDim i b A) (CType.substDim i b B) =
           CType.sigma A B
      congr 1
      · exact CType.substDim_absent_aux i b A h.1
      · exact CType.substDim_absent_aux i b B h.2
  | .glue φ T f fInv sec ret coh A, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.glue (φ.substDim i (if b then DimExpr.one else DimExpr.zero))
             (CType.substDim i b T)
             (CTerm.substDimBool i b f) (CTerm.substDimBool i b fInv)
             (CTerm.substDimBool i b sec) (CTerm.substDimBool i b ret)
             (CTerm.substDimBool i b coh)
             (CType.substDim i b A) =
           CType.glue φ T f fInv sec ret coh A
      obtain ⟨⟨⟨⟨⟨⟨⟨hφ, hT⟩, hf⟩, hfInv⟩, hsec⟩, hret⟩, hcoh⟩, hA⟩ := h
      rw [FaceFormula.substDim_of_absent i _ φ hφ,
          CType.substDim_absent_aux i b T hT,
          CTerm.substDimBool_of_absent i b f hf,
          CTerm.substDimBool_of_absent i b fInv hfInv,
          CTerm.substDimBool_of_absent i b sec hsec,
          CTerm.substDimBool_of_absent i b ret hret,
          CTerm.substDimBool_of_absent i b coh hcoh,
          CType.substDim_absent_aux i b A hA]

theorem CType.substDim_of_absent (i : DimVar) (b : Bool) (A : CType)
    (h : CType.dimAbsent i A = true) : CType.substDim i b A = A :=
  CType.substDim_absent_aux i b A h

-- ── CType.substDimExpr absent-subst (general DimExpr version) ─────────────────

private def CType.substDimExpr_absent_aux (i : DimVar) (r : DimExpr) :
    (A : CType) → CType.dimAbsent i A = true → CType.substDimExpr i r A = A
  | .univ,      _ => rfl
  | .pi A B,    h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.pi (A.substDimExpr i r) (B.substDimExpr i r) = CType.pi A B
      congr 1
      · exact CType.substDimExpr_absent_aux i r A h.1
      · exact CType.substDimExpr_absent_aux i r B h.2
  | .path A a t, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.path (A.substDimExpr i r) (a.substDim i r) (t.substDim i r) =
           CType.path A a t
      congr 1
      · exact CType.substDimExpr_absent_aux i r A h.1.1
      · exact CTerm.substDim_of_absent i r a h.1.2
      · exact CTerm.substDim_of_absent i r t h.2
  | .sigma A B, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.sigma (A.substDimExpr i r) (B.substDimExpr i r) =
           CType.sigma A B
      congr 1
      · exact CType.substDimExpr_absent_aux i r A h.1
      · exact CType.substDimExpr_absent_aux i r B h.2
  | .glue φ T f fInv sec ret coh A, h => by
      simp only [CType.dimAbsent, Bool.and_eq_true] at h
      show CType.glue (φ.substDim i r)
             (T.substDimExpr i r)
             (f.substDim i r) (fInv.substDim i r)
             (sec.substDim i r) (ret.substDim i r) (coh.substDim i r)
             (A.substDimExpr i r) =
           CType.glue φ T f fInv sec ret coh A
      obtain ⟨⟨⟨⟨⟨⟨⟨hφ, hT⟩, hf⟩, hfInv⟩, hsec⟩, hret⟩, hcoh⟩, hA⟩ := h
      rw [FaceFormula.substDim_of_absent i r φ hφ,
          CType.substDimExpr_absent_aux i r T hT,
          CTerm.substDim_of_absent i r f hf,
          CTerm.substDim_of_absent i r fInv hfInv,
          CTerm.substDim_of_absent i r sec hsec,
          CTerm.substDim_of_absent i r ret hret,
          CTerm.substDim_of_absent i r coh hcoh,
          CType.substDimExpr_absent_aux i r A hA]

/-- Generalised: when `i` is absent from `A`, substituting `i` by any `DimExpr`
    leaves `A` unchanged.  Equivalently: line reversal (via `i := inv i`) is
    a no-op on constant-in-`i` types — the fact that makes `vTranspInv` reduce
    to identity in the constant-domain case. -/
theorem CType.substDimExpr_of_absent (i : DimVar) (r : DimExpr) (A : CType)
    (h : CType.dimAbsent i A = true) : CType.substDimExpr i r A = A :=
  CType.substDimExpr_absent_aux i r A h

-- ── Constancy: at0 = at1 when binder is absent ───────────────────────────────

theorem DimLine.const_endpoints_eq (A : CType) (i : DimVar)
    (h : CType.dimAbsent i A = true) :
    (DimLine.const A i).at0 = (DimLine.const A i).at1 := by
  simp [DimLine.const_at0, DimLine.const_at1,
        CType.substDim_of_absent i false A h,
        CType.substDim_of_absent i true  A h]

-- ── Face connection ───────────────────────────────────────────────────────────

/-- On the (eq0 i) face, the line evaluates to at0. -/
theorem DimLine.atBool_eq0_face (L : DimLine) (env : DimVar → Bool)
    (h : (FaceFormula.eq0 L.binder).eval env = true) :
    L.atBool (env L.binder) = L.at0 := by
  simp only [FaceFormula.eval] at h
  cases hb : env L.binder with
  | false => simp [DimLine.atBool, DimLine.at0]
  | true  => simp [hb] at h

/-- On the (eq1 i) face, the line evaluates to at1. -/
theorem DimLine.atBool_eq1_face (L : DimLine) (env : DimVar → Bool)
    (h : (FaceFormula.eq1 L.binder).eval env = true) :
    L.atBool (env L.binder) = L.at1 := by
  simp only [FaceFormula.eval] at h
  cases hb : env L.binder with
  | false => simp [hb] at h
  | true  => simp [DimLine.atBool, DimLine.at1]

/-- For any environment, atBool gives either at0 or at1. -/
theorem DimLine.atBool_is_endpoint (L : DimLine) (env : DimVar → Bool) :
    L.atBool (env L.binder) = L.at0 ∨ L.atBool (env L.binder) = L.at1 := by
  cases env L.binder
  · left;  rfl
  · right; rfl

-- ── Substitution idempotence ──────────────────────────────────────────────────
-- After substDimBool i b, dimension i is no longer free.
-- A second substDimBool i b is then a no-op (substDimBool_of_absent).

-- Step 1: substituting i out of a DimExpr makes i absent.

theorem DimExpr.dimAbsent_after_subst
    (i : DimVar) (r : DimExpr) (hr : r.dimAbsent i = true)
    (e : DimExpr) : (DimExpr.subst i r e).dimAbsent i = true := by
  induction e with
  | zero      => rfl
  | one       => rfl
  | var j     =>
      simp only [DimExpr.subst]
      by_cases hji : j = i
      · simp [hji, hr]
      · simp only [if_neg hji, DimExpr.dimAbsent, bne_iff_ne]; exact hji
  | inv e ih  => simp [DimExpr.subst, DimExpr.dimAbsent, ih]
  | meet a b iha ihb =>
      simp only [DimExpr.subst, DimExpr.dimAbsent, iha, ihb, Bool.and_self]
  | join a b iha ihb =>
      simp only [DimExpr.subst, DimExpr.dimAbsent, iha, ihb, Bool.and_self]

-- `DimExpr.dimAbsent_endpoint` now lives in Interval.lean.

-- Step 2: after substDim i r, i is absent from any CTerm (mutual with a
-- helper `dimAbsent.clauses_after_substDim` for the compN clause list).

mutual
  private def CTerm.dimAbsent_after_substDim_aux
      (i : DimVar) (r : DimExpr) (hr : r.dimAbsent i = true) :
      (t : CTerm) → (t.substDim i r).dimAbsent i = true
    | .var _      => rfl
    | .lam _ t    => CTerm.dimAbsent_after_substDim_aux i r hr t
    | .app f a    => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent_after_substDim_aux i r hr f,
          CTerm.dimAbsent_after_substDim_aux i r hr a, Bool.and_self]
    | .plam j t   => by
        simp only [CTerm.substDim]
        by_cases hji : j = i
        · subst hji; simp [CTerm.dimAbsent]
        · simp only [if_neg hji, CTerm.dimAbsent]
          exact CTerm.dimAbsent_after_substDim_aux i r hr t
    | .papp t s   => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent_after_substDim_aux i r hr t,
          DimExpr.dimAbsent_after_subst i r hr s, Bool.and_self]
    | .transp _ _ φ t => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          FaceFormula.dimAbsent_after_substDim i r hr φ,
          CTerm.dimAbsent_after_substDim_aux i r hr t, Bool.and_self]
    | .comp _ _ φ u t => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          FaceFormula.dimAbsent_after_substDim i r hr φ,
          CTerm.dimAbsent_after_substDim_aux i r hr u,
          CTerm.dimAbsent_after_substDim_aux i r hr t, Bool.and_self]
    | .compN _ _ clauses t => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent.clauses_after_substDim i r hr clauses,
          CTerm.dimAbsent_after_substDim_aux i r hr t, Bool.and_true]
    | .glueIn φ t a => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          FaceFormula.dimAbsent_after_substDim i r hr φ,
          CTerm.dimAbsent_after_substDim_aux i r hr t,
          CTerm.dimAbsent_after_substDim_aux i r hr a, Bool.and_self]
    | .unglue φ f g => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          FaceFormula.dimAbsent_after_substDim i r hr φ,
          CTerm.dimAbsent_after_substDim_aux i r hr f,
          CTerm.dimAbsent_after_substDim_aux i r hr g, Bool.and_self]
    | .pair a b => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent_after_substDim_aux i r hr a,
          CTerm.dimAbsent_after_substDim_aux i r hr b, Bool.and_self]
    | .fst t => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent_after_substDim_aux i r hr t]
    | .snd t => by
        simp only [CTerm.substDim, CTerm.dimAbsent,
          CTerm.dimAbsent_after_substDim_aux i r hr t]

  /-- Helper: `i` is absent from every clause in the result of substituting
      `i := r` in a clause list (provided `r` doesn't mention `i`). -/
  private def CTerm.dimAbsent.clauses_after_substDim
      (i : DimVar) (r : DimExpr) (hr : r.dimAbsent i = true) :
      (clauses : List (FaceFormula × CTerm)) →
      CTerm.dimAbsent.clauses i (CTerm.substDim.clauses i r clauses) = true
    | []                 => rfl
    | (φ, u) :: rest     => by
        simp only [CTerm.substDim.clauses, CTerm.dimAbsent.clauses,
          Bool.and_eq_true]
        refine ⟨⟨?_, ?_⟩, ?_⟩
        · exact FaceFormula.dimAbsent_after_substDim i r hr φ
        · exact CTerm.dimAbsent_after_substDim_aux i r hr u
        · exact CTerm.dimAbsent.clauses_after_substDim i r hr rest
end

theorem CTerm.dimAbsent_after_substDimBool (i : DimVar) (b : Bool) (t : CTerm) :
    (t.substDimBool i b).dimAbsent i = true :=
  CTerm.dimAbsent_after_substDim_aux i _ (DimExpr.dimAbsent_endpoint i b) t

-- Step 3: after CType.substDim i b, i is absent from the type.

private def CType.dimAbsent_after_substDim_aux (i : DimVar) (b : Bool) :
    (A : CType) → (A.substDim i b).dimAbsent i = true
  | .univ       => rfl
  | .pi A B     => by
      simp only [CType.substDim, CType.dimAbsent,
        CType.dimAbsent_after_substDim_aux i b A,
        CType.dimAbsent_after_substDim_aux i b B, Bool.and_self]
  | .path A a t => by
      simp only [CType.substDim, CType.dimAbsent,
        CType.dimAbsent_after_substDim_aux i b A,
        CTerm.dimAbsent_after_substDimBool i b a,
        CTerm.dimAbsent_after_substDimBool i b t, Bool.and_self]
  | .sigma A B => by
      simp only [CType.substDim, CType.dimAbsent,
        CType.dimAbsent_after_substDim_aux i b A,
        CType.dimAbsent_after_substDim_aux i b B, Bool.and_self]
  | .glue φ T f fInv sec ret coh A => by
      simp only [CType.substDim, CType.dimAbsent,
        FaceFormula.dimAbsent_after_substDim i _
          (DimExpr.dimAbsent_endpoint i b) φ,
        CType.dimAbsent_after_substDim_aux i b T,
        CTerm.dimAbsent_after_substDimBool i b f,
        CTerm.dimAbsent_after_substDimBool i b fInv,
        CTerm.dimAbsent_after_substDimBool i b sec,
        CTerm.dimAbsent_after_substDimBool i b ret,
        CTerm.dimAbsent_after_substDimBool i b coh,
        CType.dimAbsent_after_substDim_aux i b A, Bool.and_self]

theorem CType.dimAbsent_after_substDim (i : DimVar) (b : Bool) (A : CType) :
    (A.substDim i b).dimAbsent i = true :=
  CType.dimAbsent_after_substDim_aux i b A

-- Step 4: idempotence.

/-- Substituting dimension i twice at the same Bool endpoint is idempotent:
    after the first subst i is absent; the second is a no-op. -/
theorem CTerm.substDimBool_idem (i : DimVar) (b : Bool) (t : CTerm) :
    (t.substDimBool i b).substDimBool i b = t.substDimBool i b :=
  CTerm.substDimBool_of_absent i b _ (CTerm.dimAbsent_after_substDimBool i b t)

theorem CType.substDim_idem (i : DimVar) (b : Bool) (A : CType) :
    (A.substDim i b).substDim i b = A.substDim i b :=
  CType.substDim_of_absent i b _ (CType.dimAbsent_after_substDim i b A)

-- ── Substitution commutativity ────────────────────────────────────────────────
-- Substituting at disjoint dimensions i ≠ j commutes.
-- General preconditions: r.dimAbsent j = true (r doesn't use j) and vice versa.
-- For Bool endpoints (.zero / .one) both hold trivially.

theorem DimExpr.subst_comm
    (i j : DimVar) (r s : DimExpr) (hij : i ≠ j)
    (hrj : r.dimAbsent j = true) (hsi : s.dimAbsent i = true)
    (e : DimExpr) :
    DimExpr.subst i r (DimExpr.subst j s e) =
    DimExpr.subst j s (DimExpr.subst i r e) := by
  induction e with
  | zero => rfl
  | one  => rfl
  | var k =>
      by_cases hki : k = i
      · subst hki
        -- LHS: subst i r (subst j s (var i))
        --    = subst i r (var i)  [i ≠ j]
        --    = r
        -- RHS: subst j s (subst i r (var i))
        --    = subst j s r = r    [r absent from j]
        simp only [DimExpr.subst, if_neg hij, ite_true,
                   DimExpr.subst_of_absent j s r hrj]
      · by_cases hkj : k = j
        · subst hkj
          -- LHS: subst i r (subst j s (var j))
          --    = subst i r s = s   [s absent from i]
          -- RHS: subst j s (subst i r (var j))
          --    = subst j s (var j) [j ≠ i]
          --    = s
          simp only [DimExpr.subst, if_neg hki, ite_true,
                     DimExpr.subst_of_absent i r s hsi]
        · -- k ≠ i, k ≠ j: both sides reduce to var k
          simp [DimExpr.subst, hki, hkj]
  | inv e ih   => simp [DimExpr.subst, ih]
  | meet a b iha ihb => simp [DimExpr.subst, iha, ihb]
  | join a b iha ihb => simp [DimExpr.subst, iha, ihb]

-- Bool endpoints have dimAbsent = true for every dimension.
private theorem DimExpr.subst_comm_bool
    (i j : DimVar) (b c : Bool) (hij : i ≠ j) (e : DimExpr) :
    DimExpr.subst i (if b then .one else .zero)
      (DimExpr.subst j (if c then .one else .zero) e) =
    DimExpr.subst j (if c then .one else .zero)
      (DimExpr.subst i (if b then .one else .zero) e) :=
  DimExpr.subst_comm i j _ _ hij
    (DimExpr.dimAbsent_endpoint j b) (DimExpr.dimAbsent_endpoint i c) e

-- CTerm commutativity — private def for structural recursion, mutual with
-- its clause-list helper so the .compN arm can recurse through the list.

mutual
  private def CTerm.substDim_comm_aux
      (i j : DimVar) (r s : DimExpr) (hij : i ≠ j)
      (hrj : r.dimAbsent j = true) (hsi : s.dimAbsent i = true) :
      (t : CTerm) →
      (t.substDim i r).substDim j s =
      (t.substDim j s).substDim i r
    | .var _    => rfl
    | .lam x t  => congrArg (CTerm.lam x) (CTerm.substDim_comm_aux i j r s hij hrj hsi t)
    | .app f a  => by
        simp only [CTerm.substDim]
        congr 1
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi f
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi a
    | .plam k t => by
        by_cases hki : k = i <;> by_cases hkj : k = j
        · exact absurd (hki.symm.trans hkj) hij
        · subst hki
          simp only [CTerm.substDim, ite_true, if_neg hij]
        · subst hkj
          simp only [CTerm.substDim, ite_true, if_neg (Ne.symm hij)]
        · simp only [CTerm.substDim, if_neg hki, if_neg hkj]
          exact congrArg (CTerm.plam k) (CTerm.substDim_comm_aux i j r s hij hrj hsi t)
    | .papp t e => by
        simp only [CTerm.substDim]
        rw [CTerm.substDim_comm_aux i j r s hij hrj hsi t,
            (DimExpr.subst_comm i j r s hij hrj hsi e).symm]
    | .transp k A φ t => by
        simp only [CTerm.substDim]
        congr 1
        · exact FaceFormula.substDim_comm i j r s hij hrj hsi φ
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi t
    | .comp _ _ φ u t => by
        simp only [CTerm.substDim]
        congr 1
        · exact FaceFormula.substDim_comm i j r s hij hrj hsi φ
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi u
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi t
    | .compN _ _ clauses t => by
        simp only [CTerm.substDim]
        congr 1
        · exact CTerm.substDim.clauses_comm_aux i j r s hij hrj hsi clauses
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi t
    | .glueIn φ t a => by
        simp only [CTerm.substDim]
        congr 1
        · exact FaceFormula.substDim_comm i j r s hij hrj hsi φ
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi t
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi a
    | .unglue φ f g => by
        simp only [CTerm.substDim]
        congr 1
        · exact FaceFormula.substDim_comm i j r s hij hrj hsi φ
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi f
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi g
    | .pair a b => by
        simp only [CTerm.substDim]
        congr 1
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi a
        · exact CTerm.substDim_comm_aux i j r s hij hrj hsi b
    | .fst t => by
        simp only [CTerm.substDim]
        exact congrArg CTerm.fst (CTerm.substDim_comm_aux i j r s hij hrj hsi t)
    | .snd t => by
        simp only [CTerm.substDim]
        exact congrArg CTerm.snd (CTerm.substDim_comm_aux i j r s hij hrj hsi t)

  /-- Helper: `substDim.clauses` commutes on disjoint dim variables. -/
  private def CTerm.substDim.clauses_comm_aux
      (i j : DimVar) (r s : DimExpr) (hij : i ≠ j)
      (hrj : r.dimAbsent j = true) (hsi : s.dimAbsent i = true) :
      (clauses : List (FaceFormula × CTerm)) →
      CTerm.substDim.clauses j s (CTerm.substDim.clauses i r clauses) =
      CTerm.substDim.clauses i r (CTerm.substDim.clauses j s clauses)
    | []                 => rfl
    | (φ, u) :: rest     => by
        simp only [CTerm.substDim.clauses]
        congr 1
        · exact Prod.ext
            (FaceFormula.substDim_comm i j r s hij hrj hsi φ)
            (CTerm.substDim_comm_aux i j r s hij hrj hsi u)
        · exact CTerm.substDim.clauses_comm_aux i j r s hij hrj hsi rest
end

theorem CTerm.substDimBool_comm
    (i j : DimVar) (b c : Bool) (hij : i ≠ j) (t : CTerm) :
    (t.substDimBool i b).substDimBool j c =
    (t.substDimBool j c).substDimBool i b :=
  CTerm.substDim_comm_aux i j _ _ hij
    (DimExpr.dimAbsent_endpoint j b) (DimExpr.dimAbsent_endpoint i c) t

-- CType commutativity

private def CType.substDim_comm_aux
    (i j : DimVar) (b c : Bool) (hij : i ≠ j) :
    (A : CType) →
    (A.substDim i b).substDim j c =
    (A.substDim j c).substDim i b
  | .univ     => rfl
  | .pi A B   => by
      simp only [CType.substDim]
      rw [CType.substDim_comm_aux i j b c hij A,
          CType.substDim_comm_aux i j b c hij B]
  | .path A a t => by
      simp only [CType.substDim]
      rw [CType.substDim_comm_aux i j b c hij A,
          CTerm.substDimBool_comm i j b c hij a,
          CTerm.substDimBool_comm i j b c hij t]
  | .sigma A B => by
      simp only [CType.substDim]
      rw [CType.substDim_comm_aux i j b c hij A,
          CType.substDim_comm_aux i j b c hij B]
  | .glue φ T f fInv sec ret coh A => by
      simp only [CType.substDim]
      rw [FaceFormula.substDim_comm i j _ _ hij
            (DimExpr.dimAbsent_endpoint j b) (DimExpr.dimAbsent_endpoint i c) φ,
          CType.substDim_comm_aux i j b c hij T,
          CTerm.substDimBool_comm i j b c hij f,
          CTerm.substDimBool_comm i j b c hij fInv,
          CTerm.substDimBool_comm i j b c hij sec,
          CTerm.substDimBool_comm i j b c hij ret,
          CTerm.substDimBool_comm i j b c hij coh,
          CType.substDim_comm_aux i j b c hij A]

theorem CType.substDim_comm
    (i j : DimVar) (b c : Bool) (hij : i ≠ j) (A : CType) :
    (A.substDim i b).substDim j c =
    (A.substDim j c).substDim i b :=
  CType.substDim_comm_aux i j b c hij A
