------------------------------------------------------------------------
-- A variant of declarative kinding in Fω with interval kinds
------------------------------------------------------------------------

{-# OPTIONS --safe --without-K #-}

module FOmegaInt.Kinding.Declarative where

open import Data.Context.WellFormed
open import Data.Fin using (Fin; zero)
open import Data.Fin.Substitution
open import Data.Fin.Substitution.Lemmas
open import Data.Fin.Substitution.ExtraLemmas
open import Data.Fin.Substitution.Typed
open import Data.Fin.Substitution.TypedRelation
open import Data.Nat using (ℕ)
open import Data.Product using (_,_; _×_; proj₁)
import Data.Vec as Vec
open import Function using (_∘_)
import Level
open import Relation.Binary.PropositionalEquality as PropEq hiding ([_])

open import FOmegaInt.Syntax

------------------------------------------------------------------------
-- Declarative (sub)kinding, subtyping and kind/type equality.
--
-- This module contains variants of the declarative kinding and
-- subtyping judgments which differ slightly from those given in the
-- Typing module.  Some of the rules in this module have additional
-- premises which we call "validity conditions".  These validity
-- conditions are redundant in the sense that they follow (more or
-- less) directly from so-called "validity" properties of the
-- corresponding judgments.  For example, the kinding rule (∈-Π-e)
-- below concludes that `Γ ⊢ a · b ∈ k Kind[ b ]' and has, among
-- others, the validity condition `Γ ⊢ k Kind[ b ] kd' as one of its
-- premises.  But this is a direct consequence of "kinding validity"
-- which states that the kinds of well-kinded types are themselves
-- well-formed, i.e. that `Γ ⊢ k kd' whenever `Γ ⊢Tp a ∈ k' (see the
-- Kinding.Declarative.Validity module for the full set of validity
-- lemmas).  Since all the validity conditions follow from
-- corresponding validity lemmas, they are, in principle, redundant.
-- Indeed, this is why they do not appear in the kinding and subtyping
-- rules of the Typing module.  The reason for including the validity
-- conditions as premises in the rules below is that the proofs of
-- some lemmas (notably the validity lemmas themselves) are done by
-- induction on kinding and subtyping derivations and require the
-- proofs of these conditions to be proper sub-derivations of the
-- derivations on which the induction is performed.
--
-- To proof the validity lemmas for both variants of the kinding and
-- subtyping judgments, we use the following strategy:
--
--  1. prove the validity lemmas for the judgments containing the
--     additional validity conditions as premises,
--
--  2. prove that the two variants of the judgments are
--     equivalent, i.e.
--
--     a) the extended judgments are sound w.r.t to the original
--        judgments: we can drop the validity conditions without
--        affecting the conclusion of the derivations, and
--
--     b) the extended judgments are complete w.r.t. to the original
--        judgments: crucially, the additional validity conditions
--        follow from the *remaining premises* of the extended rules
--        via the validity lemmas proved in step 1,
--
--  3. prove that validity holds for the original judgments via the
--     equivalence: convert to the extended judgment (via
--     completeness), apply the lemma in question, convert the
--     conclusion back (via soundness).
--
--  See the Kinding.Declarative.Equivalence module for the full proof
--  of equivalence (point 2) and the `Tp∈-valid' lemma in the
--  Typing.Inversion module for an example of point 3.

module Kinding where
  open TermCtx
  open Syntax
  open Substitution using (_[_]; _Kind[_]; weaken)

  infix 4 _ctx _⊢_kd _⊢_wf
  infix 4 _⊢Tp_∈_ _⊢_∈_
  infix 4 _⊢_<:_∈_ _⊢_<∷_
  infix 4 _⊢_≃_∈_ _⊢_≅_ _⊢_≃⊎≡_∈_

  mutual

    -- Well-formed typing contexts.

    _ctx : ∀ {n} → Ctx n → Set
    _ctx = ContextFormation._wf _⊢_wf

    -- Well-formed type/kind ascriptions in typing contexts.
    data _⊢_wf {n} (Γ : Ctx n) : TermAsc n → Set where
      wf-kd : ∀ {a} → Γ ⊢ a kd    → Γ ⊢ (kd a) wf
      wf-tp : ∀ {a} → Γ ⊢Tp a ∈ * → Γ ⊢ (tp a) wf

    -- Well-formed kinds.
    data _⊢_kd {n} (Γ : Ctx n) : Kind Term n → Set where
      kd-⋯ : ∀ {a b} → Γ ⊢Tp a ∈ * → Γ ⊢Tp b ∈ * → Γ ⊢ a ⋯ b kd
      kd-Π : ∀ {j k} → Γ ⊢ j kd → kd j ∷ Γ ⊢ k kd → Γ ⊢ Π j k kd

    -- Kinding derivations.
    data _⊢Tp_∈_ {n} (Γ : Ctx n) : Term n → Kind Term n → Set where
      ∈-var : ∀ {k} x → Γ ctx → lookup Γ x ≡ kd k → Γ ⊢Tp var x ∈ k
      ∈-⊥-f : Γ ctx → Γ ⊢Tp ⊥ ∈ *
      ∈-⊤-f : Γ ctx → Γ ⊢Tp ⊤ ∈ *
      ∈-∀-f : ∀ {k a} → Γ ⊢ k kd → kd k ∷ Γ ⊢Tp a ∈ * → Γ ⊢Tp Π k a ∈ *
      ∈-→-f : ∀ {a b} → Γ ⊢Tp a ∈ * → Γ ⊢Tp b ∈ * → Γ ⊢Tp a ⇒ b ∈ *
      ∈-Π-i : ∀ {j a k} → Γ ⊢ j kd → kd j ∷ Γ ⊢Tp a ∈ k →
              -- Validity condition:
              kd j ∷ Γ ⊢ k kd →
              Γ ⊢Tp Λ j a ∈ Π j k
      ∈-Π-e : ∀ {a b j k} → Γ ⊢Tp a ∈ Π j k → Γ ⊢Tp b ∈ j →
              -- Validity conditions:
              kd j ∷ Γ ⊢ k kd → Γ ⊢ k Kind[ b ] kd →
              Γ ⊢Tp a · b ∈ k Kind[ b ]
      ∈-s-i : ∀ {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢Tp a ∈ a ⋯ a
      ∈-⇑   : ∀ {a j k} → Γ ⊢Tp a ∈ j → Γ ⊢ j <∷ k → Γ ⊢Tp a ∈ k

    -- Subkinding derivations.
    data _⊢_<∷_ {n} (Γ : Ctx n) : Kind Term n → Kind Term n → Set where
      <∷-⋯ : ∀ {a₁ a₂ b₁ b₂} →
             Γ ⊢ a₂ <: a₁ ∈ * → Γ ⊢ b₁ <: b₂ ∈ * → Γ ⊢ a₁ ⋯ b₁ <∷ a₂ ⋯ b₂
      <∷-Π : ∀ {j₁ j₂ k₁ k₂} →
             Γ ⊢ j₂ <∷ j₁ → kd j₂ ∷ Γ ⊢ k₁ <∷ k₂ → Γ ⊢ Π j₁ k₁ kd →
             Γ ⊢ Π j₁ k₁ <∷ Π j₂ k₂

    -- Subtyping derivations.
    data _⊢_<:_∈_ {n} (Γ : Ctx n) : Term n → Term n → Kind Term n → Set where
      <:-refl  : ∀ {a k}     → Γ ⊢Tp a ∈ k → Γ ⊢ a <: a ∈ k
      <:-trans : ∀ {a b c k} → Γ ⊢ a <: b ∈ k → Γ ⊢ b <: c ∈ k → Γ ⊢ a <: c ∈ k
      <:-β₁    : ∀ {j a k b} → kd j ∷ Γ ⊢Tp a ∈ k → Γ ⊢Tp b ∈ j →
                 -- Validity conditions:
                 Γ ⊢Tp a [ b ] ∈ k Kind[ b ] →
                 kd j ∷ Γ ⊢ k kd → Γ ⊢ k Kind[ b ] kd →
                 Γ ⊢ (Λ j a) · b <: a [ b ] ∈ k Kind[ b ]
      <:-β₂    : ∀ {j a k b} → kd j ∷ Γ ⊢Tp a ∈ k → Γ ⊢Tp b ∈ j →
                 -- Validity conditions:
                 Γ ⊢Tp a [ b ] ∈ k Kind[ b ] →
                 kd j ∷ Γ ⊢ k kd → Γ ⊢ k Kind[ b ] kd →
                 Γ ⊢ a [ b ] <: (Λ j a) · b ∈ k Kind[ b ]
      <:-η₁    : ∀ {a j k} → Γ ⊢Tp a ∈ Π j k →
                 Γ ⊢ Λ j (weaken a · var zero) <: a ∈ Π j k
      <:-η₂    : ∀ {a j k} → Γ ⊢Tp a ∈ Π j k →
                 Γ ⊢ a <: Λ j (weaken a · var zero) ∈ Π j k
      <:-⊥     : ∀ {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢ ⊥ <: a ∈ *
      <:-⊤     : ∀ {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢ a <: ⊤ ∈ *
      <:-∀     : ∀ {k₁ k₂ a₁ a₂} →
                 Γ ⊢ k₂ <∷ k₁ → kd k₂ ∷ Γ ⊢ a₁ <: a₂ ∈ * → Γ ⊢Tp Π k₁ a₁ ∈ * →
                 Γ ⊢ Π k₁ a₁ <: Π k₂ a₂ ∈ *
      <:-→     : ∀ {a₁ a₂ b₁ b₂} → Γ ⊢ a₂ <: a₁ ∈ * → Γ ⊢ b₁ <: b₂ ∈ * →
                 Γ ⊢ a₁ ⇒ b₁ <: a₂ ⇒ b₂ ∈ *
      <:-λ     : ∀ {j₁ j₂ a₁ a₂ j k} → kd j ∷ Γ ⊢ a₁ <: a₂ ∈ k →
                 Γ ⊢Tp Λ j₁ a₁ ∈ Π j k → Γ ⊢Tp Λ j₂ a₂ ∈ Π j k →
                 Γ ⊢ Λ j₁ a₁ <: Λ j₂ a₂ ∈ Π j k
      <:-·     : ∀ {a₁ a₂ b₁ b₂ j k} → Γ ⊢ a₁ <: a₂ ∈ Π j k → Γ ⊢ b₁ ≃ b₂ ∈ j →
                 -- Validity conditions:
                 Γ ⊢Tp b₁ ∈ j → kd j ∷ Γ ⊢ k kd → Γ ⊢ k Kind[ b₁ ] kd →
                 Γ ⊢ a₁ · b₁ <: a₂ · b₂ ∈ k Kind[ b₁ ]
      <:-⟨|    : ∀ {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢ b <: a ∈ *
      <:-|⟩    : ∀ {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢ a <: c ∈ *
      <:-⋯-i   : ∀ {a b c d} → Γ ⊢ a <: b ∈ c ⋯ d → Γ ⊢ a <: b ∈ a ⋯ b
      <:-⇑     : ∀ {a b j k} → Γ ⊢ a <: b ∈ j → Γ ⊢ j <∷ k → Γ ⊢ a <: b ∈ k

    -- Type equality.
    data _⊢_≃_∈_ {n} (Γ : Ctx n) : Term n → Term n → Kind Term n → Set where
      <:-antisym : ∀ {a b k} → Γ ⊢ a <: b ∈ k → Γ ⊢ b <: a ∈ k → Γ ⊢ a ≃ b ∈ k

  -- Kind equality.
  data _⊢_≅_ {n} (Γ : Ctx n) : Kind Term n → Kind Term n → Set where
    <∷-antisym : ∀ {j k} → Γ ⊢ j <∷ k → Γ ⊢ k <∷ j → Γ ⊢ j ≅ k

  -- Combined kinding of types and term variable typing.
  data _⊢_∈_ {n} (Γ : Ctx n) : Term n → TermAsc n → Set where
    ∈-tp  : ∀ {a k} → Γ ⊢Tp a ∈ k → Γ ⊢ a ∈ kd k
    ∈-var : ∀ x {a} → Γ ctx → lookup Γ x ≡ tp a → Γ ⊢ var x ∈ tp a

  -- Combined type equality and syntactic term variable equality (used
  -- for well-formed equality lifted to substitutions).
  data _⊢_≃⊎≡_∈_ {n} (Γ : Ctx n) : Term n → Term n → TermAsc n → Set where
    ≃-tp  : ∀ {a b k} → Γ ⊢ a ≃ b ∈ k           → Γ ⊢ a     ≃⊎≡ b     ∈ kd k
    ≃-var : ∀ x {a} → Γ ctx → lookup Γ x ≡ tp a → Γ ⊢ var x ≃⊎≡ var x ∈ tp a

  open PropEq using ([_])

  -- Derived variable rules.

  ∈-var′ : ∀ {n} {Γ : Ctx n} x → Γ ctx → Γ ⊢ var x ∈ lookup Γ x
  ∈-var′ {Γ = Γ} x Γ-ctx with lookup Γ x | inspect (lookup Γ) x
  ∈-var′ x Γ-ctx | kd k | [ Γ[x]≡kd-k ] = ∈-tp (∈-var x Γ-ctx Γ[x]≡kd-k)
  ∈-var′ x Γ-ctx | tp a | [ Γ[x]≡tp-a ] = ∈-var x Γ-ctx Γ[x]≡tp-a

  ≃⊎≡-var : ∀ {n} {Γ : Ctx n} x → Γ ctx → Γ ⊢ var x ≃⊎≡ var x ∈ lookup Γ x
  ≃⊎≡-var {Γ = Γ} x Γ-ctx with lookup Γ x | inspect (lookup Γ) x
  ≃⊎≡-var x Γ-ctx | kd k | [ Γ[x]≡kd-k ] =
    let x∈a = ∈-var x Γ-ctx Γ[x]≡kd-k
    in ≃-tp (<:-antisym (<:-refl x∈a) (<:-refl x∈a))
  ≃⊎≡-var x Γ-ctx | tp a | [ Γ[x]≡tp-a ] = ≃-var x Γ-ctx Γ[x]≡tp-a

  open ContextFormation _⊢_wf public
    hiding (_wf) renaming (_⊢_wfExt to _⊢_ext)

------------------------------------------------------------------------
-- Properties of typings

open Syntax
open TermCtx
open Kinding

-- An inversion lemma for _⊢_wf.

wf-kd-inv : ∀ {n} {Γ : Ctx n} {k} → Γ ⊢ kd k wf → Γ ⊢ k kd
wf-kd-inv (wf-kd k-kd) = k-kd

-- Subkinds have the same shape.

<∷-⌊⌋ : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j <∷ k → ⌊ j ⌋ ≡ ⌊ k ⌋
<∷-⌊⌋ (<∷-⋯ _ _)             = refl
<∷-⌊⌋ (<∷-Π j₂<∷j₁ k₁<∷k₂ _) = cong₂ _⇒_ (sym (<∷-⌊⌋ j₂<∷j₁)) (<∷-⌊⌋ k₁<∷k₂)

-- Equal kinds have the same shape.

≅-⌊⌋ : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j ≅ k → ⌊ j ⌋ ≡ ⌊ k ⌋
≅-⌊⌋ (<∷-antisym j<∷k k<∷j) = <∷-⌊⌋ j<∷k

-- Kind and type equality imply subkinding and subtyping, respectively.

≅⇒<∷ : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j ≅ k → Γ ⊢ j <∷ k
≅⇒<∷ (<∷-antisym j<∷k k<∷j) = j<∷k

≃⇒<: : ∀ {n} {Γ : Ctx n} {a b k} → Γ ⊢ a ≃ b ∈ k → Γ ⊢ a <: b ∈ k
≃⇒<: (<:-antisym a<:b b<:a) = a<:b

-- Reflexivity of subkinding.
<∷-refl : ∀ {n} {Γ : Ctx n} {k} → Γ ⊢ k kd → Γ ⊢ k <∷ k
<∷-refl (kd-⋯ a∈* b∈*)   = <∷-⋯ (<:-refl a∈*) (<:-refl b∈*)
<∷-refl (kd-Π j-kd k-kd) = <∷-Π (<∷-refl j-kd) (<∷-refl k-kd) (kd-Π j-kd k-kd)

-- Reflexivity of kind equality.
≅-refl : ∀ {n} {Γ : Ctx n} {k} → Γ ⊢ k kd → Γ ⊢ k ≅ k
≅-refl k-kd = <∷-antisym (<∷-refl k-kd) (<∷-refl k-kd)

-- Symmetry of kind equality.
≅-sym : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j ≅ k → Γ ⊢ k ≅ j
≅-sym (<∷-antisym j<∷k k<∷j) = <∷-antisym k<∷j j<∷k

-- An admissible kind equality rule for interval kinds.
≅-⋯ : ∀ {n} {Γ : Ctx n} {a₁ a₂ b₁ b₂} →
      Γ ⊢ a₁ ≃ a₂ ∈ * → Γ ⊢ b₁ ≃ b₂ ∈ * → Γ ⊢ a₁ ⋯ b₁ ≅ a₂ ⋯ b₂
≅-⋯ (<:-antisym a₁<:a₂ a₂<:a₁) (<:-antisym b₁<:b₂ b₂<:b₁) =
  <∷-antisym (<∷-⋯ a₂<:a₁ b₁<:b₂) (<∷-⋯ a₁<:a₂ b₂<:b₁)

-- Type equality is reflexive.
≃-refl : ∀ {n} {Γ : Ctx n} {a k} → Γ ⊢Tp a ∈ k → Γ ⊢ a ≃ a ∈ k
≃-refl a∈k = <:-antisym (<:-refl a∈k) (<:-refl a∈k)

-- Type equality is transitive.
≃-trans : ∀ {n} {Γ : Ctx n} {a b c k} →
          Γ ⊢ a ≃ b ∈ k → Γ ⊢ b ≃ c ∈ k → Γ ⊢ a ≃ c ∈ k
≃-trans (<:-antisym a<:b b<:a) (<:-antisym b<:c c<:b) =
  <:-antisym (<:-trans a<:b b<:c) (<:-trans c<:b b<:a)

-- Type equality is symmetric.
≃-sym : ∀ {n} {Γ : Ctx n} {a b k} → Γ ⊢ a ≃ b ∈ k → Γ ⊢ b ≃ a ∈ k
≃-sym (<:-antisym a<:b b<:a) = <:-antisym b<:a a<:b

-- Reflexivity of the combined type and term variable equality.
≃⊎≡-refl : ∀ {n} {Γ : Ctx n} {a b} → Γ ⊢ a ∈ b → Γ ⊢ a ≃⊎≡ a ∈ b
≃⊎≡-refl (∈-tp a∈k)                = ≃-tp (≃-refl a∈k)
≃⊎≡-refl (∈-var x Γ-ctx Γ[x]≡tp-a) = ≃-var x Γ-ctx Γ[x]≡tp-a

-- Types inhabiting interval kinds are proper Types.
Tp∈-⋯-* : ∀ {n} {Γ : Ctx n} {a b c} → Γ ⊢Tp a ∈ b ⋯ c → Γ ⊢Tp a ∈ *
Tp∈-⋯-* a∈b⋯c = ∈-⇑ (∈-s-i a∈b⋯c) (<∷-⋯ (<:-⊥ a∈b⋯c) (<:-⊤ a∈b⋯c))

-- Well-formedness of the * kind.
*-kd : ∀ {n} {Γ : Ctx n} → Γ ctx → Γ ⊢ * kd
*-kd Γ-ctx = kd-⋯ (∈-⊥-f Γ-ctx) (∈-⊤-f Γ-ctx)

module _ where
  open Substitution

  -- An admissible β-rule for type equality.
  ≃-β : ∀ {n} {Γ : Ctx n} {j a k b} → kd j ∷ Γ ⊢Tp a ∈ k → Γ ⊢Tp b ∈ j →
        -- Validity conditions:
        Γ ⊢Tp a [ b ] ∈ k Kind[ b ] → kd j ∷ Γ ⊢ k kd → Γ ⊢ k Kind[ b ] kd →
        Γ ⊢ (Λ j a) · b ≃ a [ b ] ∈ k Kind[ b ]
  ≃-β a∈k b∈j a[b]∈k[b] k-kd k[b]-kd =
    <:-antisym (<:-β₁ a∈k b∈j a[b]∈k[b] k-kd k[b]-kd)
               (<:-β₂ a∈k b∈j a[b]∈k[b] k-kd k[b]-kd)

  -- An admissible η-rule for type equality.
  ≃-η : ∀ {n} {Γ : Ctx n} {a j k} →
        Γ ⊢Tp a ∈ Π j k → Γ ⊢ Λ j (weaken a · var zero) ≃ a ∈ Π j k
  ≃-η a∈Πjk = <:-antisym (<:-η₁ a∈Πjk) (<:-η₂ a∈Πjk)

-- An admissible congruence rule for type equality w.r.t. formation of
-- arrow types.
≃-→ : ∀ {n} {Γ : Ctx n} {a₁ a₂ b₁ b₂} →
      Γ ⊢ a₁ ≃ a₂ ∈ * → Γ ⊢ b₁ ≃ b₂ ∈ * → Γ ⊢ a₁ ⇒ b₁ ≃ a₂ ⇒ b₂ ∈ *
≃-→ (<:-antisym a₁<:a₂∈* a₂<:a₁∈*) (<:-antisym b₁<:b₂∈* b₂<:b₁∈*) =
  <:-antisym (<:-→ a₂<:a₁∈* b₁<:b₂∈*) (<:-→ a₁<:a₂∈* b₂<:b₁∈*)

-- An admissible congruence rule for type equality w.r.t. operator
-- abstraction.
≃-λ : ∀ {n} {Γ : Ctx n} {j₁ j₂ a₁ a₂ j k} →
      kd j ∷ Γ ⊢ a₁ ≃ a₂ ∈ k → Γ ⊢Tp Λ j₁ a₁ ∈ Π j k → Γ ⊢Tp Λ j₂ a₂ ∈ Π j k →
      Γ ⊢ Λ j₁ a₁ ≃ Λ j₂ a₂ ∈ Π j k
≃-λ (<:-antisym a₁<:a₂∈k a₂<:a₁∈k) Λj₁a₁∈Πjk Λj₂a₂∈Πjk =
  <:-antisym (<:-λ a₁<:a₂∈k Λj₁a₁∈Πjk Λj₂a₂∈Πjk)
             (<:-λ a₂<:a₁∈k Λj₂a₂∈Πjk Λj₁a₁∈Πjk)

-- An admissible subsumption rule for type equality.
≃-⇑ : ∀ {n} {Γ : Ctx n} {a b j k} → Γ ⊢ a ≃ b ∈ j → Γ ⊢ j <∷ k → Γ ⊢ a ≃ b ∈ k
≃-⇑ (<:-antisym a<:b b<:a) j<∷k = <:-antisym (<:-⇑ a<:b j<∷k) (<:-⇑ b<:a j<∷k)

-- The contexts of all the above judgments are well-formed.

mutual

  kd-ctx : ∀ {n} {Γ : Ctx n} {k} → Γ ⊢ k kd → Γ ctx
  kd-ctx (kd-⋯ a∈* b∈*)    = Tp∈-ctx a∈*
  kd-ctx (kd-Π j-kd  k-kd) = kd-ctx j-kd

  Tp∈-ctx : ∀ {n} {Γ : Ctx n} {a k} → Γ ⊢Tp a ∈ k → Γ ctx
  Tp∈-ctx (∈-var x Γ-ctx Γ[x]≡kd-k) = Γ-ctx
  Tp∈-ctx (∈-⊥-f Γ-ctx)             = Γ-ctx
  Tp∈-ctx (∈-⊤-f Γ-ctx)             = Γ-ctx
  Tp∈-ctx (∈-∀-f k-kd a∈*)          = kd-ctx k-kd
  Tp∈-ctx (∈-→-f a∈* b∈*)           = Tp∈-ctx a∈*
  Tp∈-ctx (∈-Π-i j-kd a∈k k-kd)     = kd-ctx j-kd
  Tp∈-ctx (∈-Π-e a∈Πjk b∈j k-kd k[b]-kd) = Tp∈-ctx a∈Πjk
  Tp∈-ctx (∈-s-i a∈b⋯c)             = Tp∈-ctx a∈b⋯c
  Tp∈-ctx (∈-⇑ a∈j j<∷k)            = Tp∈-ctx a∈j

wf-ctx : ∀ {n} {Γ : Ctx n} {a} → Γ ⊢ a wf → Γ ctx
wf-ctx (wf-kd k-kd) = kd-ctx k-kd
wf-ctx (wf-tp a∈*)  = Tp∈-ctx a∈*

<:-ctx : ∀ {n} {Γ : Ctx n} {a b k} → Γ ⊢ a <: b ∈ k → Γ ctx
<:-ctx (<:-refl a∈k)            = Tp∈-ctx a∈k
<:-ctx (<:-trans a<:b∈k b<:c∈k) = <:-ctx a<:b∈k
<:-ctx (<:-β₁ a∈j b∈k a[b]∈k[b] k-kd k[b]-kd) = Tp∈-ctx b∈k
<:-ctx (<:-β₂ a∈j b∈k a[b]∈k[b] k-kd k[b]-kd) = Tp∈-ctx b∈k
<:-ctx (<:-η₁ a∈Πjk) = Tp∈-ctx a∈Πjk
<:-ctx (<:-η₂ a∈Πjk) = Tp∈-ctx a∈Πjk
<:-ctx (<:-⊥ a∈b⋯c)  = Tp∈-ctx a∈b⋯c
<:-ctx (<:-⊤ a∈b⋯c)  = Tp∈-ctx a∈b⋯c
<:-ctx (<:-∀ k₂<∷k₁ a₁<:a₂ ∀k₁a₁∈*) = Tp∈-ctx ∀k₁a₁∈*
<:-ctx (<:-→ a₂<:a₁ b₁<:b₂)         = <:-ctx a₂<:a₁
<:-ctx (<:-λ a₂<:a₁∈Πjk Λa₁k₁∈Πjk Λa₂k₂∈Πjk)        = Tp∈-ctx Λa₁k₁∈Πjk
<:-ctx (<:-· a₂<:a₁∈Πjk b₂≃b₁∈j b₁∈j k-kd k[b₁]-kd) = <:-ctx a₂<:a₁∈Πjk
<:-ctx (<:-⟨| a∈b⋯c)      = Tp∈-ctx a∈b⋯c
<:-ctx (<:-|⟩ a∈b⋯c)      = Tp∈-ctx a∈b⋯c
<:-ctx (<:-⋯-i a<:b∈c⋯d)  = <:-ctx a<:b∈c⋯d
<:-ctx (<:-⇑ a<:b∈j j<∷k) = <:-ctx a<:b∈j

<∷-ctx : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j <∷ k → Γ ctx
<∷-ctx (<∷-⋯ a₂<:a₁ b₁<:b₂)          = <:-ctx a₂<:a₁
<∷-ctx (<∷-Π j₂<∷j₁ k₁<∷k₂ Πj₁k₁-kd) = <∷-ctx j₂<∷j₁

≅-ctx : ∀ {n} {Γ : Ctx n} {j k} → Γ ⊢ j ≅ k → Γ ctx
≅-ctx (<∷-antisym j<∷k k<∷j) = <∷-ctx j<∷k

≃-ctx : ∀ {n} {Γ : Ctx n} {a b k} → Γ ⊢ a ≃ b ∈ k → Γ ctx
≃-ctx (<:-antisym a<:b∈k b<:a∈k) = <:-ctx a<:b∈k

∈-ctx : ∀ {n} {Γ : Ctx n} {a b} → Γ ⊢ a ∈ b → Γ ctx
∈-ctx (∈-tp a∈k)                = Tp∈-ctx a∈k
∈-ctx (∈-var x Γ-ctx Γ[x]≡tp-a) = Γ-ctx

≃⊎≡-ctx : ∀ {n} {Γ : Ctx n} {a b c} → Γ ⊢ a ≃⊎≡ b ∈ c → Γ ctx
≃⊎≡-ctx (≃-tp a≃b∈k)              = ≃-ctx a≃b∈k
≃⊎≡-ctx (≃-var x Γ-ctx Γ[x]≡tp-a) = Γ-ctx

----------------------------------------------------------------------
-- Well-kinded/typed substitutions (i.e. substitution lemmas)

-- A shorthand for kindings and typings of Ts by kind or type
-- ascriptions.
TermAscTyping : (ℕ → Set) → Set₁
TermAscTyping T = Typing TermAsc T TermAsc Level.zero

-- Liftings from well-typed Ts to well-typed/kinded terms/types.
LiftTo-∈ : ∀ {T} → TermAscTyping T → Set₁
LiftTo-∈ _⊢T_∈_ = LiftTyped Substitution.termAscTermSubst _⊢_wf _⊢T_∈_ _⊢_∈_

-- Helper lemmas about untyped T-substitutions in raw terms and kinds.

record TypedSubstAppHelpers {T} (rawLift : Lift T Term) : Set where
  open Substitution using (weaken; _[_]; _Kind[_])
  module A = SubstApp rawLift
  module L = Lift     rawLift

  field
    -- Substitutions in kinds and types commute.

    Kind/-sub-↑ : ∀ {m n} k a (σ : Sub T m n) →
                  k Kind[ a ] A.Kind/ σ ≡ (k A.Kind/ σ L.↑) Kind[ a A./ σ ]

    /-sub-↑ : ∀ {m n} b a (σ : Sub T m n) →
              b [ a ] A./ σ ≡ (b A./ σ L.↑) [ a A./ σ ]

    -- Weakening of terms commutes with substitution in terms.

    weaken-/ : ∀ {m n} {σ : Sub T m n} a →
               weaken (a A./ σ) ≡ weaken a A./ σ L.↑

-- Application of generic well-typed T-substitutions to all the judgments.

module TypedSubstApp {T : ℕ → Set} (_⊢T_∈_ : TermAscTyping T)
                     (liftTyped : LiftTo-∈ _⊢T_∈_)
                     (helpers : TypedSubstAppHelpers
                                  (LiftTyped.rawLift liftTyped))
                     where

  open LiftTyped liftTyped renaming (lookup to /∈-lookup)
  open TypedSubstAppHelpers helpers

  -- Lift well-kinded Ts to well-kinded types.
  liftTp : ∀ {n} {Γ : Ctx n} {a k kd-k} →
           kd-k ≡ kd k → Γ ⊢T a ∈ kd-k → Γ ⊢Tp L.lift a ∈ k
  liftTp refl a∈kd-k with ∈-lift a∈kd-k
  liftTp refl a∈kd-k | ∈-tp a∈k = a∈k

  mutual

    -- Substitutions preserve well-formedness of kinds and
    -- well-kindedness of types.

    kd-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {k σ} →
           Γ ⊢ k kd → Δ ⊢/ σ ∈ Γ → Δ ⊢ k A.Kind/ σ kd
    kd-/ (kd-⋯ a∈* b∈*)   σ∈Γ = kd-⋯ (Tp∈-/ a∈* σ∈Γ) (Tp∈-/ b∈* σ∈Γ)
    kd-/ (kd-Π j-kd k-kd) σ∈Γ =
      kd-Π j/σ-kd (kd-/ k-kd (∈-↑ (wf-kd j/σ-kd) σ∈Γ))
      where j/σ-kd = kd-/ j-kd σ∈Γ

    Tp∈-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a k σ} →
            Γ ⊢Tp a ∈ k → Δ ⊢/ σ ∈ Γ → Δ ⊢Tp a A./ σ ∈ k A.Kind/ σ
    Tp∈-/ (∈-var x Γ-ctx Γ[x]≡kd-k) σ∈Γ =
      liftTp (cong (_/ _) Γ[x]≡kd-k) (/∈-lookup σ∈Γ x)
    Tp∈-/ (∈-⊥-f Γ-ctx)    σ∈Γ = ∈-⊥-f (/∈-wf σ∈Γ)
    Tp∈-/ (∈-⊤-f Γ-ctx)    σ∈Γ = ∈-⊤-f (/∈-wf σ∈Γ)
    Tp∈-/ (∈-∀-f k-kd a∈*) σ∈Γ =
      ∈-∀-f k/σ-kd (Tp∈-/ a∈* (∈-↑ (wf-kd k/σ-kd) σ∈Γ))
      where k/σ-kd = kd-/ k-kd σ∈Γ
    Tp∈-/ (∈-→-f a∈* b∈*)  σ∈Γ = ∈-→-f (Tp∈-/ a∈* σ∈Γ) (Tp∈-/ b∈* σ∈Γ)
    Tp∈-/ (∈-Π-i j-kd a∈k k-kd) σ∈Γ =
      ∈-Π-i j/σ-kd (Tp∈-/ a∈k σ↑∈j∷Γ) (kd-/ k-kd σ↑∈j∷Γ)
      where
        j/σ-kd = kd-/ j-kd σ∈Γ
        σ↑∈j∷Γ = ∈-↑ (wf-kd j/σ-kd) σ∈Γ
    Tp∈-/ (∈-Π-e {_} {b} {_} {k} a∈Πjk b∈j k-kd k[b]-kd) σ∈Γ =
      subst (_ ⊢Tp _ ∈_) (sym k[b]/σ≡k/σ[b/σ])
            (∈-Π-e (Tp∈-/ a∈Πjk σ∈Γ) (Tp∈-/ b∈j σ∈Γ)
                   (kd-/ k-kd (∈-↑ (kd-/-wf k-kd σ∈Γ) σ∈Γ))
                   (subst (_ ⊢_kd) k[b]/σ≡k/σ[b/σ] (kd-/ k[b]-kd σ∈Γ)))
      where k[b]/σ≡k/σ[b/σ] = Kind/-sub-↑ k b _
    Tp∈-/ (∈-s-i a∈b⋯c)  σ∈Γ = ∈-s-i (Tp∈-/ a∈b⋯c σ∈Γ)
    Tp∈-/ (∈-⇑ a∈j j<∷k) σ∈Γ = ∈-⇑ (Tp∈-/ a∈j σ∈Γ) (<∷-/ j<∷k σ∈Γ)

    -- Substitutions commute with subkinding, subtyping and type
    -- equality.

    <∷-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {j k σ} →
           Γ ⊢ j <∷ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ j A.Kind/ σ <∷ k A.Kind/ σ
    <∷-/ (<∷-⋯ a₂<:a₁ b₁<:b₂) σ∈Γ = <∷-⋯ (<:-/ a₂<:a₁ σ∈Γ) (<:-/ b₁<:b₂ σ∈Γ)
    <∷-/ (<∷-Π j₂<∷j₁ k₁<∷k₂ Πj₁k₁-kd) σ∈Γ =
      <∷-Π (<∷-/ j₂<∷j₁ σ∈Γ) (<∷-/ k₁<∷k₂ (∈-↑ (<∷-/-wf k₁<∷k₂ σ∈Γ) σ∈Γ))
           (kd-/ Πj₁k₁-kd σ∈Γ)

    <:-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a b k σ} →
           Γ ⊢ a <: b ∈ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ a A./ σ <: b A./ σ ∈ k A.Kind/ σ
    <:-/ (<:-refl a∈k)            σ∈Γ = <:-refl (Tp∈-/ a∈k σ∈Γ)
    <:-/ (<:-trans a<:b∈k b<:c∈k) σ∈Γ =
      <:-trans (<:-/ a<:b∈k σ∈Γ) (<:-/ b<:c∈k σ∈Γ)
    <:-/ (<:-β₁ {j} {a} {k} {b} a∈k b∈j a[b]∈k[b] k-kd k[b]-kd) σ∈Γ =
      subst₂ (_ ⊢ (Λ j a) · b A./ _ <:_∈_)
             (sym a[b]/σ≡a/σ[b/σ]) (sym k[b]/σ≡k/σ[b/σ])
             (<:-β₁ (Tp∈-/ a∈k σ↑∈j∷Γ) (Tp∈-/ b∈j σ∈Γ)
                    (subst₂ (_ ⊢Tp_∈_) a[b]/σ≡a/σ[b/σ] k[b]/σ≡k/σ[b/σ]
                            (Tp∈-/ a[b]∈k[b] σ∈Γ))
                    (kd-/ k-kd σ↑∈j∷Γ)
                    (subst (_ ⊢_kd) k[b]/σ≡k/σ[b/σ] (kd-/ k[b]-kd σ∈Γ)))
      where
        σ↑∈j∷Γ          = ∈-↑ (Tp∈-/-wf a∈k σ∈Γ) σ∈Γ
        k[b]/σ≡k/σ[b/σ] = Kind/-sub-↑ k b _
        a[b]/σ≡a/σ[b/σ] = /-sub-↑ a b _
    <:-/ (<:-β₂ {j} {a} {k} {b} a∈k b∈j a[b]∈k[b] k-kd k[b]-kd) σ∈Γ =
      subst₂ (_ ⊢_<: (Λ j a) · b A./ _ ∈_)
             (sym a[b]/σ≡a/σ[b/σ]) (sym k[b]/σ≡k/σ[b/σ])
             (<:-β₂ (Tp∈-/ a∈k σ↑∈j∷Γ) (Tp∈-/ b∈j σ∈Γ)
                    (subst₂ (_ ⊢Tp_∈_) a[b]/σ≡a/σ[b/σ] k[b]/σ≡k/σ[b/σ]
                            (Tp∈-/ a[b]∈k[b] σ∈Γ))
                    (kd-/ k-kd σ↑∈j∷Γ)
                    (subst (_ ⊢_kd) k[b]/σ≡k/σ[b/σ] (kd-/ k[b]-kd σ∈Γ)))
      where
        σ↑∈j∷Γ          = ∈-↑ (Tp∈-/-wf a∈k σ∈Γ) σ∈Γ
        k[b]/σ≡k/σ[b/σ] = Kind/-sub-↑ k b _
        a[b]/σ≡a/σ[b/σ] = /-sub-↑ a b _
    <:-/ {Δ = Δ} {σ = σ} (<:-η₁ {a} {j} {k} a∈Πjk) σ∈Γ =
      subst (Δ ⊢_<: a A./ σ ∈ Π j k A.Kind/ σ)
            (cong (Λ _) (cong₂ _·_ (weaken-/ a) (sym (lift-var zero))))
            (<:-η₁ (Tp∈-/ a∈Πjk σ∈Γ))
    <:-/ {Δ = Δ} {σ = σ} (<:-η₂  {a} {j} {k} a∈Πjk) σ∈Γ =
      subst (Δ ⊢ a A./ σ <:_∈ Π j k A.Kind/ σ)
            (cong (Λ _) (cong₂ _·_ (weaken-/ a) (sym (lift-var zero))))
            (<:-η₂ (Tp∈-/ a∈Πjk σ∈Γ))
    <:-/ (<:-⊥ a∈b⋯c) σ∈Γ = <:-⊥ (Tp∈-/ a∈b⋯c σ∈Γ)
    <:-/ (<:-⊤ a∈b⋯c) σ∈Γ = <:-⊤ (Tp∈-/ a∈b⋯c σ∈Γ)
    <:-/ (<:-∀ k₂<∷k₁ a₁<:a₂∈* ∀j₁k₁∈*) σ∈Γ =
      <:-∀ (<∷-/ k₂<∷k₁ σ∈Γ) (<:-/ a₁<:a₂∈* (∈-↑ (<:-/-wf a₁<:a₂∈* σ∈Γ) σ∈Γ))
           (Tp∈-/ ∀j₁k₁∈* σ∈Γ)
    <:-/ (<:-→ a₂<:a₁∈* b₁<:b₂∈*) σ∈Γ =
      <:-→ (<:-/ a₂<:a₁∈* σ∈Γ) (<:-/ b₁<:b₂∈* σ∈Γ)
    <:-/ (<:-λ a₁<:a₂∈k Λj₁a₁∈Πjk Λj₂a₂∈Πjk) σ∈Γ =
      <:-λ (<:-/ a₁<:a₂∈k (∈-↑ (<:-/-wf a₁<:a₂∈k σ∈Γ) σ∈Γ))
           (Tp∈-/ Λj₁a₁∈Πjk σ∈Γ) (Tp∈-/ Λj₂a₂∈Πjk σ∈Γ)
    <:-/ (<:-· {k = k} a₁<:a₂∈Πjk b₁≃b₂∈j b₁∈j k-kd k[b₁]-kd) σ∈Γ =
      subst (_ ⊢ _ <: _ ∈_) (sym k[b₁]/σ≡k/σ[b₁/σ])
            (<:-· (<:-/ a₁<:a₂∈Πjk σ∈Γ) (≃-/ b₁≃b₂∈j σ∈Γ)
                  (Tp∈-/ b₁∈j σ∈Γ) (kd-/ k-kd (∈-↑ (kd-/-wf k-kd σ∈Γ) σ∈Γ))
                  (subst (_ ⊢_kd) k[b₁]/σ≡k/σ[b₁/σ] (kd-/ k[b₁]-kd σ∈Γ)))
      where k[b₁]/σ≡k/σ[b₁/σ] = Kind/-sub-↑ k _ _
    <:-/ (<:-⟨| a∈b⋯c)      σ∈Γ = <:-⟨| (Tp∈-/ a∈b⋯c σ∈Γ)
    <:-/ (<:-|⟩ a∈b⋯c)      σ∈Γ = <:-|⟩ (Tp∈-/ a∈b⋯c σ∈Γ)
    <:-/ (<:-⋯-i a<:b∈c⋯d)  σ∈Γ = <:-⋯-i (<:-/ a<:b∈c⋯d σ∈Γ)
    <:-/ (<:-⇑ a<:b∈j j<∷k) σ∈Γ = <:-⇑ (<:-/ a<:b∈j σ∈Γ) (<∷-/ j<∷k σ∈Γ)

    ≃-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a b k σ} →
          Γ ⊢ a ≃ b ∈ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ a A./ σ ≃ b A./ σ ∈ k A.Kind/ σ
    ≃-/ (<:-antisym a<:b∈k b<:a∈k) σ∈Γ =
      <:-antisym (<:-/ a<:b∈k σ∈Γ) (<:-/ b<:a∈k σ∈Γ)

    -- Helpers (to satisfy the termination checker).

    kd-/-wf : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {j k σ} →
              kd j ∷ Γ ⊢ k kd → Δ ⊢/ σ ∈ Γ → Δ ⊢ kd (j A.Kind/ σ) wf
    kd-/-wf (kd-⋯ a∈* _)  σ∈Γ = Tp∈-/-wf a∈* σ∈Γ
    kd-/-wf (kd-Π j-kd _) σ∈Γ = kd-/-wf j-kd σ∈Γ

    Tp∈-/-wf : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a j k σ} →
               kd j ∷ Γ ⊢Tp a ∈ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ kd (j A.Kind/ σ) wf
    Tp∈-/-wf (∈-var x (wf-kd k-kd ∷ Γ-ctx) _) σ∈Γ = wf-kd (kd-/ k-kd σ∈Γ)
    Tp∈-/-wf (∈-⊥-f (wf-kd j-kd ∷ Γ-ctx)) σ∈Γ = wf-kd (kd-/ j-kd σ∈Γ)
    Tp∈-/-wf (∈-⊤-f (wf-kd j-kd ∷ Γ-ctx)) σ∈Γ = wf-kd (kd-/ j-kd σ∈Γ)
    Tp∈-/-wf (∈-∀-f k-kd _)      σ∈Γ = kd-/-wf k-kd σ∈Γ
    Tp∈-/-wf (∈-→-f a∈* _)       σ∈Γ = Tp∈-/-wf a∈* σ∈Γ
    Tp∈-/-wf (∈-Π-i j-kd _ _)    σ∈Γ = kd-/-wf j-kd σ∈Γ
    Tp∈-/-wf (∈-Π-e a∈Πjk _ _ _) σ∈Γ = Tp∈-/-wf a∈Πjk σ∈Γ
    Tp∈-/-wf (∈-s-i a∈b⋯c)       σ∈Γ = Tp∈-/-wf a∈b⋯c σ∈Γ
    Tp∈-/-wf (∈-⇑ a∈b _)         σ∈Γ = Tp∈-/-wf a∈b σ∈Γ

    <:-/-wf : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a b j k σ} →
              kd j ∷ Γ ⊢ a <: b ∈ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ kd (j A.Kind/ σ) wf
    <:-/-wf (<:-refl a∈k)        σ∈Γ = Tp∈-/-wf a∈k σ∈Γ
    <:-/-wf (<:-trans a<:b _)    σ∈Γ = <:-/-wf a<:b σ∈Γ
    <:-/-wf (<:-β₁ _ b∈j _ _ _)  σ∈Γ = Tp∈-/-wf b∈j σ∈Γ
    <:-/-wf (<:-β₂ _ b∈j _ _ _)  σ∈Γ = Tp∈-/-wf b∈j σ∈Γ
    <:-/-wf (<:-η₁ a∈Πjk)        σ∈Γ = Tp∈-/-wf a∈Πjk σ∈Γ
    <:-/-wf (<:-η₂ a∈Πjk)        σ∈Γ = Tp∈-/-wf a∈Πjk σ∈Γ
    <:-/-wf (<:-⊥ a∈b⋯c)         σ∈Γ = Tp∈-/-wf a∈b⋯c σ∈Γ
    <:-/-wf (<:-⊤ a∈b⋯c)         σ∈Γ = Tp∈-/-wf a∈b⋯c σ∈Γ
    <:-/-wf (<:-∀ j₂<∷j₁ _ _)    σ∈Γ = <∷-/-wf j₂<∷j₁ σ∈Γ
    <:-/-wf (<:-→ a₂<:a₁∈* _)    σ∈Γ = <:-/-wf a₂<:a₁∈* σ∈Γ
    <:-/-wf (<:-λ _ Λj₁a₂∈Πjk _) σ∈Γ = Tp∈-/-wf Λj₁a₂∈Πjk σ∈Γ
    <:-/-wf (<:-· a₁<:a₂∈Πjk _ _ _ _) σ∈Γ = <:-/-wf a₁<:a₂∈Πjk σ∈Γ
    <:-/-wf (<:-⟨| a∈b⋯c)        σ∈Γ = Tp∈-/-wf a∈b⋯c σ∈Γ
    <:-/-wf (<:-|⟩ a∈b⋯c)        σ∈Γ = Tp∈-/-wf a∈b⋯c σ∈Γ
    <:-/-wf (<:-⋯-i a<:b∈c⋯d)    σ∈Γ = <:-/-wf a<:b∈c⋯d σ∈Γ
    <:-/-wf (<:-⇑ a<:b∈k _)      σ∈Γ = <:-/-wf a<:b∈k σ∈Γ

    <∷-/-wf : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {j k l σ} →
              kd j ∷ Γ ⊢ k <∷ l → Δ ⊢/ σ ∈ Γ → Δ ⊢ kd (j A.Kind/ σ) wf
    <∷-/-wf (<∷-⋯ j₂<:j₁ _)   σ∈Γ = <:-/-wf j₂<:j₁ σ∈Γ
    <∷-/-wf (<∷-Π j₂<∷j₁ _ _) σ∈Γ = <∷-/-wf j₂<∷j₁ σ∈Γ

  -- Substitutions preserve well-formedness of ascriptions.
  wf-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a σ} →
         Γ ⊢ a wf → Δ ⊢/ σ ∈ Γ → Δ ⊢ a A.TermAsc/ σ wf
  wf-/ (wf-kd k-kd) σ∈Γ = wf-kd (kd-/ k-kd σ∈Γ)
  wf-/ (wf-tp a∈b)  σ∈Γ = wf-tp (Tp∈-/ a∈b σ∈Γ)

  -- Substitutions commute with kind equality.
  ≅-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {j k σ} →
        Γ ⊢ j ≅ k → Δ ⊢/ σ ∈ Γ → Δ ⊢ j A.Kind/ σ ≅ k A.Kind/ σ
  ≅-/ (<∷-antisym j<∷k k<∷j) σ∈Γ = <∷-antisym (<∷-/ j<∷k σ∈Γ) (<∷-/ k<∷j σ∈Γ)

  -- Substitutions preserve well-kindedness and well-typedness.
  ∈-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a b σ} →
        Γ ⊢ a ∈ b → Δ ⊢/ σ ∈ Γ → Δ ⊢ a A./ σ ∈ b A.TermAsc/ σ
  ∈-/ (∈-tp a∈b)                σ∈Γ = ∈-tp (Tp∈-/ a∈b σ∈Γ)
  ∈-/ (∈-var x Γ-ctx Γ[x]≡tp-a) σ∈Γ =
    subst (_ ⊢ _ ∈_) (cong (A._TermAsc/ _) Γ[x]≡tp-a)
          (∈-lift (/∈-lookup σ∈Γ x))

  -- Substitutions preserve type and syntactic term equality.
  ≃⊎≡-/ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a b c σ} →
          Γ ⊢ a ≃⊎≡ b ∈ c → Δ ⊢/ σ ∈ Γ →
          Δ ⊢ a A./ σ ≃⊎≡ b A./ σ ∈ c A.TermAsc/ σ
  ≃⊎≡-/ (≃-tp a≃b∈k) σ∈Γ              = ≃-tp (≃-/ a≃b∈k σ∈Γ)
  ≃⊎≡-/ (≃-var x Γ-ctx Γ[x]≡tp-a) σ∈Γ =
    let x/σ∈tp-a/σ = subst (_ ⊢ _ ∈_) (cong (A._TermAsc/ _) Γ[x]≡tp-a)
                           (∈-lift (/∈-lookup σ∈Γ x))
    in ≃⊎≡-refl x/σ∈tp-a/σ

-- Well-kinded type substitutions.
module KindedSubstitution where
  open Substitution           using (simple; termSubst)
  open SimpleExt    simple    using (extension)
  open TermSubst    termSubst using (varLift; termLift)

  private
    module S  = Substitution
    module KL = TermLikeLemmas S.termLikeLemmasKind

  -- Helper lemmas about untyped renamings and substitutions in terms
  -- and kinds.

  varHelpers : TypedSubstAppHelpers varLift
  varHelpers = record
    { Kind/-sub-↑ = KL./-sub-↑
    ; /-sub-↑     = LiftSubLemmas./-sub-↑ S.varLiftSubLemmas
    ; weaken-/    = LiftAppLemmas.wk-commutes S.varLiftAppLemmas
    }

  termHelpers : TypedSubstAppHelpers termLift
  termHelpers = record
    { Kind/-sub-↑ = λ k _ _ → KL.sub-commutes k
    ; /-sub-↑     = λ a _ _ → S.sub-commutes a
    ; weaken-/    = S.weaken-/
    }

  -- Kinded type substitutions.

  typedTermSubst : TypedTermSubst TermAsc Term Level.zero TypedSubstAppHelpers
  typedTermSubst = record
    { _⊢_wf = _⊢_wf
    ; _⊢_∈_ = _⊢_∈_
    ; termLikeLemmas = S.termLikeLemmasTermAsc
    ; varHelpers     = varHelpers
    ; termHelpers    = termHelpers
    ; wf-wf    = wf-ctx
    ; ∈-wf     = ∈-ctx
    ; ∈-var    = ∈-var′
    ; typedApp = TypedSubstApp.∈-/
    }
  open TypedTermSubst typedTermSubst public
    hiding (_⊢_wf; _⊢_∈_; varHelpers; termHelpers; ∈-var; ∈-/Var; ∈-/)
    renaming (lookup to /∈-lookup)
  open TypedSubstApp _⊢Var_∈_ varLiftTyped varHelpers public using () renaming
    ( wf-/  to wf-/Var
    ; kd-/  to kd-/Var
    ; Tp∈-/ to Tp∈-/Var
    ; <∷-/  to <∷-/Var
    ; <:-/  to <:-/Var
    ; ∈-/   to ∈-/Var
    ; ≃⊎≡-/ to ≃⊎≡-/Var
    )
  open Substitution using (weaken; weakenKind; weakenTermAsc)

  -- Weakening preserves the various judgments.

  wf-weaken : ∀ {n} {Γ : Ctx n} {a b} → Γ ⊢ a wf → Γ ⊢ b wf →
              (a ∷ Γ) ⊢ weakenTermAsc b wf
  wf-weaken a-wf b-wf = wf-/Var b-wf (Var∈-wk a-wf)

  kd-weaken : ∀ {n} {Γ : Ctx n} {a k} → Γ ⊢ a wf → Γ ⊢ k kd →
              (a ∷ Γ) ⊢ weakenKind k kd
  kd-weaken a-wf k-kd = kd-/Var k-kd (Var∈-wk a-wf)

  Tp∈-weaken : ∀ {n} {Γ : Ctx n} {a b k} → Γ ⊢ a wf → Γ ⊢Tp b ∈ k →
               (a ∷ Γ) ⊢Tp weaken b ∈ weakenKind k
  Tp∈-weaken a-wf b∈k = Tp∈-/Var b∈k (Var∈-wk a-wf)

  <∷-weaken : ∀ {n} {Γ : Ctx n} {a j k} → Γ ⊢ a wf → Γ ⊢ j <∷ k →
              (a ∷ Γ) ⊢ weakenKind j <∷ weakenKind k
  <∷-weaken a-wf j<∷k = <∷-/Var j<∷k (Var∈-wk a-wf)

  <:-weaken : ∀ {n} {Γ : Ctx n} {a b c k} → Γ ⊢ a wf → Γ ⊢ b <: c ∈ k →
              (a ∷ Γ) ⊢ weaken b <: weaken c ∈ weakenKind k
  <:-weaken a-wf b<:c∈k = <:-/Var b<:c∈k (Var∈-wk a-wf)

  ∈-weaken : ∀ {n} {Γ : Ctx n} {a b c} → Γ ⊢ a wf → Γ ⊢ b ∈ c →
             (a ∷ Γ) ⊢ weaken b ∈ weakenTermAsc c
  ∈-weaken a-wf b∈c = ∈-/Var b∈c (Var∈-wk a-wf)

  -- Weakening preserves type and syntactic term equality.
  ≃⊎≡-weaken : ∀ {n} {Γ : Ctx n} {a b c d} → Γ ⊢ a wf → Γ ⊢ b ≃⊎≡ c ∈ d →
               (a ∷ Γ) ⊢ weaken b ≃⊎≡ weaken c ∈ weakenTermAsc d
  ≃⊎≡-weaken a-wf b≃⊎≡c∈d = ≃⊎≡-/Var b≃⊎≡c∈d (Var∈-wk a-wf)

  open TypedSubstApp _⊢_∈_ termLiftTyped termHelpers public
  open Substitution using (_/_; _Kind/_; id; sub; _↑⋆_; _[_]; _Kind[_])

  -- Substitution of a single well-typed term or well-kinded type
  -- preserves the various judgments.

  kd-[] : ∀ {n} {Γ : Ctx n} {a b k} →
          b ∷ Γ ⊢ k kd → Γ ⊢ a ∈ b → Γ ⊢ k Kind[ a ] kd
  kd-[] k-kd a∈b = kd-/ k-kd (∈-sub a∈b)

  ≅-[] : ∀ {n} {Γ : Ctx n} {j k a b} →
         b ∷ Γ ⊢ j ≅ k → Γ ⊢ a ∈ b → Γ ⊢ j Kind[ a ] ≅ k Kind[ a ]
  ≅-[] j≅k a∈b = ≅-/ j≅k (∈-sub a∈b)

  Tp∈-[] : ∀ {n} {Γ : Ctx n} {a b k c} →
           c ∷ Γ ⊢Tp a ∈ k → Γ ⊢ b ∈ c → Γ ⊢Tp a [ b ] ∈ k Kind[ b ]
  Tp∈-[] a∈k b∈c = Tp∈-/ a∈k (∈-sub b∈c)

  -- Context narrowing.

  -- A typed substitution that narrows the kind of the first type
  -- variable.
  ∈-<∷-sub : ∀ {n} {Γ : Ctx n} {j k} →
             Γ ⊢ j kd → Γ ⊢ j <∷ k → kd j ∷ Γ ⊢/ id ∈ kd k ∷ Γ
  ∈-<∷-sub j-kd j<∷k =
    ∈-tsub (∈-tp (∈-⇑ (∈-var zero (j-wf ∷ Γ-ctx) refl) (<∷-weaken j-wf j<∷k)))
    where
      j-wf  = wf-kd j-kd
      Γ-ctx = kd-ctx j-kd

  -- Narrowing the kind of the first type variable preserves
  -- well-formedness of kinds.
  ⇓-kd : ∀ {n} {Γ : Ctx n} {j₁ j₂ k} →
         Γ ⊢ j₁ kd → Γ ⊢ j₁ <∷ j₂ → kd j₂ ∷ Γ ⊢ k kd → kd j₁ ∷ Γ ⊢ k kd
  ⇓-kd j₁-kd j₁<∷j₂ k-kd =
    subst (_ ⊢_kd) (KL.id-vanishes _) (kd-/ k-kd (∈-<∷-sub j₁-kd j₁<∷j₂))

  -- Narrowing the kind of the first type variable preserves
  -- well-kindedness.
  ⇓-Tp∈ : ∀ {n} {Γ : Ctx n} {j₁ j₂ a k} →
          Γ ⊢ j₁ kd → Γ ⊢ j₁ <∷ j₂ → kd j₂ ∷ Γ ⊢Tp a ∈ k → kd j₁ ∷ Γ ⊢Tp a ∈ k
  ⇓-Tp∈ j₁-kd j₁<∷j₂ a∈k =
    subst₂ (_ ⊢Tp_∈_) (S.id-vanishes _) (KL.id-vanishes _)
           (Tp∈-/ a∈k (∈-<∷-sub j₁-kd j₁<∷j₂))

  -- Narrowing the kind of the first type variable preserves
  -- subkinding and subtyping.

  ⇓-<∷ : ∀ {n} {Γ : Ctx n} {j₁ j₂ k₁ k₂} →
         Γ ⊢ j₁ kd → Γ ⊢ j₁ <∷ j₂ → kd j₂ ∷ Γ ⊢ k₁ <∷ k₂ → kd j₁ ∷ Γ ⊢ k₁ <∷ k₂
  ⇓-<∷ j₁-kd j₁<∷j₂ k₁<∷k₂ =
    subst₂ (_ ⊢_<∷_) (KL.id-vanishes _) (KL.id-vanishes _)
           (<∷-/ k₁<∷k₂ (∈-<∷-sub j₁-kd j₁<∷j₂))

  ⇓-<: : ∀ {n} {Γ : Ctx n} {j₁ j₂ a₁ a₂ k} →
         Γ ⊢ j₁ kd → Γ ⊢ j₁ <∷ j₂ → kd j₂ ∷ Γ ⊢ a₁ <: a₂ ∈ k →
         kd j₁ ∷ Γ ⊢ a₁ <: a₂ ∈ k
  ⇓-<: j₁-kd j₁<∷j₂ a₁<:a₂∈k =
    subst (_ ⊢ _ <: _ ∈_) (KL.id-vanishes _)
          (subst₂ (_ ⊢_<:_∈ _) (S.id-vanishes _) (S.id-vanishes _)
                  (<:-/ a₁<:a₂∈k (∈-<∷-sub j₁-kd j₁<∷j₂)))

-- Operations on well-formed contexts that require weakening of
-- well-formedness judgments.

module WfCtxOps where
  wfWeakenOps : WellFormedWeakenOps weakenOps
  wfWeakenOps = record { wf-weaken = KindedSubstitution.wf-weaken }

  open WellFormedWeakenOps wfWeakenOps public renaming (lookup to lookup-wf)

  -- Lookup the kind of a type variable in a well-formed context.
  lookup-kd : ∀ {m} {Γ : Ctx m} {k} x →
              Γ ctx → TermCtx.lookup Γ x ≡ kd k → Γ ⊢ k kd
  lookup-kd x Γ-ctx Γ[x]≡kd-k =
    wf-kd-inv (subst (_ ⊢_wf) Γ[x]≡kd-k (lookup-wf Γ-ctx x))

open KindedSubstitution
open WfCtxOps

-- Transitivity of subkinding.
<∷-trans : ∀ {n} {Γ : Ctx n} {j k l} → Γ ⊢ j <∷ k → Γ ⊢ k <∷ l → Γ ⊢ j <∷ l
<∷-trans (<∷-⋯ a₂<:a₁ b₁<:b₂) (<∷-⋯ a₃<:a₂ b₂<:b₃) =
  <∷-⋯ (<:-trans a₃<:a₂ a₂<:a₁) (<:-trans b₁<:b₂ b₂<:b₃)
<∷-trans (<∷-Π j₂<∷j₁ k₁<∷k₂ Πj₁k₁-kd) (<∷-Π j₃<∷j₂ k₂<∷k₃ Πj₂k₂-kd) =
  <∷-Π (<∷-trans j₃<∷j₂ j₂<∷j₁)
       (<∷-trans (⇓-<∷ (wf-kd-inv (wf-∷₁ (<∷-ctx k₂<∷k₃))) j₃<∷j₂ k₁<∷k₂)
                 k₂<∷k₃)
       Πj₁k₁-kd

-- Transitivity of kind equality.
≅-trans : ∀ {n} {Γ : Ctx n} {j k l} → Γ ⊢ j ≅ k → Γ ⊢ k ≅ l → Γ ⊢ j ≅ l
≅-trans (<∷-antisym k₁<∷k₂ k₂<∷k₁) (<∷-antisym k₂<∷k₃ k₃<∷k₂) =
  <∷-antisym (<∷-trans k₁<∷k₂ k₂<∷k₃) (<∷-trans k₃<∷k₂ k₂<∷k₁)

----------------------------------------------------------------------
-- Well-formed equality lifted to substitutions

module WfSubstitutionEquality where
  open Substitution        hiding (subst)
  open TermSubst termSubst using  (termLift)
  open ZipUnzipSimple simple simple
  open TypedSubstAppHelpers KindedSubstitution.termHelpers
  private
    module KL = TermLikeLemmas termLikeLemmasKind
    module AL = TermLikeLemmas termLikeLemmasTermAsc
    module S  = SimpleExt simple
    module Z  = SimpleExt zippedSimple

  -- Well-formed equal substitutions: pairs of substitutions that are
  -- point-wise equal.

  wfEqSub : TypedSubRel TermAsc Term Level.zero
  wfEqSub = record
    { typedRelation       = record { _⊢_wf = _⊢_wf ; _⊢_∼_∈_ = _⊢_≃⊎≡_∈_ }
    ; typeExtension       = TermCtx.weakenOps
    ; typeTermApplication = record { _/_ = λ a ρσ → a TermAsc/ (π₁ ρσ) }
    ; termSimple          = simple
    }
  open TypedSubRel wfEqSub public using ()
    renaming (_⊢/_∼_∈_ to _⊢/_≃_∈_)

  -- Simple substitutions of well-formed equal terms and types.

  wfEqWeakenOps : TypedRelWeakenOps wfEqSub
  wfEqWeakenOps = record
    { ∼∈-weaken  = ≃⊎≡-weaken
    ; ∼∈-wf      = ≃⊎≡-ctx
    ; /-wk       = /-wk-π₁
    ; /-weaken   = λ {m} {n} {στ} a → /-weaken-π₁ {m} {n} {στ} a
    ; weaken-/-∷ = AL.weaken-/-∷
    }
    where
      open ≡-Reasoning

      /-wk-π₁ : ∀ {n} {a : TermAsc n} → a TermAsc/ π₁ Z.wk ≡ weakenTermAsc a
      /-wk-π₁ {_} {a} = begin
        a TermAsc/ π₁ Z.wk   ≡⟨ cong ((a TermAsc/_) ∘ proj₁) (sym wk-unzip) ⟩
        a TermAsc/ wk        ≡⟨ AL./-wk ⟩
        weakenTermAsc a      ∎

      /-weaken-π₁ : ∀ {m n} {στ : Sub (Term ⊗ Term) m n} a →
                    a TermAsc/ π₁ (Vec.map Z.weaken στ) ≡
                      a TermAsc/ π₁ στ TermAsc/ π₁ Z.wk
      /-weaken-π₁ {_} {_} {στ} a = begin
          a TermAsc/ π₁ (Vec.map Z.weaken στ)
        ≡˘⟨ cong ((a TermAsc/_) ∘ proj₁) (map-weaken-unzip στ) ⟩
          a TermAsc/ Vec.map S.weaken (π₁ στ)
        ≡⟨ AL./-weaken a ⟩
          a TermAsc/ π₁ στ TermAsc/ S.wk
        ≡⟨ cong ((a TermAsc/ π₁ στ TermAsc/_) ∘ proj₁) wk-unzip ⟩
          a TermAsc/ π₁ στ TermAsc/ π₁ Z.wk
        ∎

  wfEqSimple : TypedRelSimple wfEqSub
  wfEqSimple = record
    { typedRelWeakenOps = wfEqWeakenOps
    ; ∼∈-var            = ≃⊎≡-var
    ; wf-wf             = wf-ctx
    ; id-vanishes       = id-vanishes-π₁
    }
    where
      open TypedRelWeakenOps wfEqWeakenOps
      open ≡-Reasoning

      id-vanishes-π₁ : ∀ {n} (a : TermAsc n) → a TermAsc/ π₁ Z.id ≡ a
      id-vanishes-π₁ a = begin
        a TermAsc/ π₁ Z.id   ≡⟨ cong ((a TermAsc/_) ∘ proj₁) (sym id-unzip) ⟩
        a TermAsc/ id        ≡⟨ AL.id-vanishes a ⟩
        a                    ∎

  open TypedRelSimple wfEqSimple public
    hiding (typedRelWeakenOps; ∼∈-var; ∼∈-weaken; ∼∈-wf; wf-wf)
    renaming (lookup to ≃-lookup)

  infixl 4  _⊢/_≃′_∈_

  -- An inversion lemma for the generic term/type equality.
  ≃⊎≡-kd-inv : ∀ {n} {Γ : Ctx n} {a b k kd-k} →
               kd-k ≡ kd k → Γ ⊢ a ≃⊎≡ b ∈ kd-k → Γ ⊢ a ≃ b ∈ k
  ≃⊎≡-kd-inv refl (≃-tp a≃b∈k) = a≃b∈k

  -- A shorthand.
  --
  -- TODO: explain why we use this particular representation of equal
  -- substitutions here.
  _⊢/_≃′_∈_ : ∀ {m n} → Ctx n → Sub Term m n → Sub Term m n → Ctx m → Set
  Δ ⊢/ σ ≃′ ρ ∈ Γ = Δ ⊢/ σ ∈ Γ × Δ ⊢/ ρ ∈ Γ × Δ ⊢/ σ ≃ ρ ∈ Γ × Δ ⊢/ ρ ≃ σ ∈ Γ

  -- Symmetry of substitution equality.
  ≃′-sym : ∀ {m n Δ Γ} {ρ σ : Sub Term m n} → Δ ⊢/ ρ ≃′ σ ∈ Γ → Δ ⊢/ σ ≃′ ρ ∈ Γ
  ≃′-sym (ρ∈Γ , σ∈Γ , ρ≃σ∈Γ , σ≃ρ∈Γ) = σ∈Γ , ρ∈Γ , σ≃ρ∈Γ , ρ≃σ∈Γ

  -- Lift a pair of equal substitutions over an additional type
  -- variable.
  ≃′-↑ : ∀ {m n Γ Δ} {ρ σ : Sub Term m n} {j k} →
         Δ ⊢ j kd → Δ ⊢ j ≅ k Kind/ ρ → Δ ⊢ j ≅ k Kind/ σ → Δ ⊢/ ρ ≃′ σ ∈ Γ →
         kd j ∷ Δ ⊢/ ρ ↑ ≃′ σ ↑ ∈ kd k ∷ Γ
  ≃′-↑ j-kd j≅k/ρ j≅k/σ (ρ∈Γ , σ∈Γ , ρ≃σ∈ , σ≃ρ∈) =
    ∈-/∷ (∈-tp z∈k/ρ) ρ∈Γ , ∈-/∷ (∈-tp z∈k/σ) σ∈Γ ,
    ∼∈-/∷ (≃-tp (≃-refl z∈k/ρ′)) ρ≃σ∈ , ∼∈-/∷ (≃-tp (≃-refl z∈k/σ′)) σ≃ρ∈
    where
      j-wf    = wf-kd j-kd
      j∷Δ-ctx = j-wf ∷ kd-ctx j-kd
      z∈k/ρ   = ∈-⇑ (∈-var zero j∷Δ-ctx refl) (<∷-weaken j-wf (≅⇒<∷ j≅k/ρ))
      z∈k/σ   = ∈-⇑ (∈-var zero j∷Δ-ctx refl) (<∷-weaken j-wf (≅⇒<∷ j≅k/σ))
      z∈k/ρ′  = subst (_ ⊢Tp _ ∈_)
                      (cong (weakenKind ∘ (_ Kind/_)) (sym (π₁-zip _ _))) z∈k/ρ
      z∈k/σ′  = subst (_ ⊢Tp _ ∈_)
                      (cong (weakenKind ∘ (_ Kind/_)) (sym (π₁-zip _ _))) z∈k/σ

  -- Lemmas about equal substitutions (weak versions).

  mutual

    -- Equal substitutions map well-formed kinds to kind equations.
    kd-/≃ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {k ρ σ} →
            Γ ⊢ k kd → Δ ⊢/ ρ ≃′ σ ∈ Γ → Δ ⊢ k Kind/ ρ ≅ k Kind/ σ
    kd-/≃ (kd-⋯ a∈* b∈*)   ρ≃σ∈Γ = ≅-⋯ (Tp∈-/≃ a∈* ρ≃σ∈Γ) (Tp∈-/≃ b∈* ρ≃σ∈Γ)
    kd-/≃ (kd-Π j-kd k-kd) ρ≃σ∈Γ =
      let ρ∈Γ , σ∈Γ , _ = ρ≃σ∈Γ
          j/ρ-kd  = kd-/ j-kd ρ∈Γ
          j/σ-kd  = kd-/ j-kd σ∈Γ
          j/ρ≅j/σ = kd-/≃ j-kd ρ≃σ∈Γ
          k/ρ≅k/σ = kd-/≃ k-kd
                          (≃′-↑ j/σ-kd (≅-sym j/ρ≅j/σ) (≅-refl j/σ-kd) ρ≃σ∈Γ)
          k/σ≅k/ρ = kd-/≃ k-kd
                          (≃′-↑ j/ρ-kd j/ρ≅j/σ (≅-refl j/ρ-kd) (≃′-sym ρ≃σ∈Γ))
          Πjk/ρ-kd = kd-/ (kd-Π j-kd k-kd) ρ∈Γ
          Πjk/σ-kd = kd-/ (kd-Π j-kd k-kd) σ∈Γ
      in <∷-antisym (<∷-Π (≅⇒<∷ (≅-sym j/ρ≅j/σ)) (≅⇒<∷ k/ρ≅k/σ) Πjk/ρ-kd)
                    (<∷-Π (≅⇒<∷ j/ρ≅j/σ) (≅⇒<∷ k/σ≅k/ρ) Πjk/σ-kd)

    -- Equal substitutions map well-kinded types to type equations.
    Tp∈-/≃ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a k ρ σ} →
             Γ ⊢Tp a ∈ k → Δ ⊢/ ρ ≃′ σ ∈ Γ → Δ ⊢ a / ρ ≃ a / σ ∈ k Kind/ ρ
    Tp∈-/≃ {ρ = ρ} {σ} (∈-var x Γ-ctx Γ[x]≡kd-k) (_ , _ , ρ≃σ∈Γ , _) =
      (≃⊎≡-kd-inv (cong (_TermAsc/ ρ) Γ[x]≡kd-k)
                  (subst (_ ⊢ _ ≃⊎≡ _ ∈_)
                         (cong (_ TermAsc/_) (π₁-zip ρ σ))
                         (≃-lookup ρ≃σ∈Γ x)))
    Tp∈-/≃ (∈-⊥-f Γ-ctx) (ρ∈Γ , _) = ≃-refl (∈-⊥-f (/∈-wf ρ∈Γ))
    Tp∈-/≃ (∈-⊤-f Γ-ctx) (ρ∈Γ , _) = ≃-refl (∈-⊤-f (/∈-wf ρ∈Γ))
    Tp∈-/≃ (∈-∀-f k-kd a∈*) ρ≃σ∈Γ =
      let ρ∈Γ , σ∈Γ , _ = ρ≃σ∈Γ
          k/ρ-kd    = kd-/ k-kd ρ∈Γ
          k/σ-kd    = kd-/ k-kd σ∈Γ
          k/ρ≅k/σ   = kd-/≃ k-kd ρ≃σ∈Γ
          a/ρ≃a/σ∈* = Tp∈-/≃ a∈* (≃′-↑ k/σ-kd (≅-sym k/ρ≅k/σ)
                                       (≅-refl k/σ-kd) ρ≃σ∈Γ)
          a/σ≃a/ρ∈* = Tp∈-/≃ a∈* (≃′-↑ k/ρ-kd k/ρ≅k/σ
                                       (≅-refl k/ρ-kd) (≃′-sym ρ≃σ∈Γ))
          ∀ka/ρ∈*   = Tp∈-/ (∈-∀-f k-kd a∈*) ρ∈Γ
          ∀ka/σ∈*   = Tp∈-/ (∈-∀-f k-kd a∈*) σ∈Γ
      in <:-antisym (<:-∀ (≅⇒<∷ (≅-sym k/ρ≅k/σ)) (≃⇒<: a/ρ≃a/σ∈*) ∀ka/ρ∈*)
                    (<:-∀ (≅⇒<∷ k/ρ≅k/σ) (≃⇒<: a/σ≃a/ρ∈*) ∀ka/σ∈*)
    Tp∈-/≃ (∈-→-f a∈* b∈*) ρ≃σ∈Γ = ≃-→ (Tp∈-/≃ a∈* ρ≃σ∈Γ) (Tp∈-/≃ b∈* ρ≃σ∈Γ)
    Tp∈-/≃ (∈-Π-i j-kd a∈k k-kd) ρ≃σ∈Γ =
      let ρ∈Γ , σ∈Γ , _ = ρ≃σ∈Γ
          j/ρ-kd        = kd-/ j-kd ρ∈Γ
          j/σ-kd        = kd-/ j-kd σ∈Γ
          j/ρ≅j/σ       = kd-/≃ j-kd ρ≃σ∈Γ
          ρ↑∈j∷Γ        = ∈-↑ (wf-kd j/ρ-kd) ρ∈Γ
          σ↑∈j∷Γ        = ∈-↑ (wf-kd j/σ-kd) σ∈Γ
          ρ↑≃σ↑∈j∷Γ     = ≃′-↑ j/ρ-kd (≅-refl j/ρ-kd) j/ρ≅j/σ ρ≃σ∈Γ
          a/ρ∈k/ρ       = Tp∈-/ a∈k ρ↑∈j∷Γ
          a/σ∈k/σ       = Tp∈-/ a∈k σ↑∈j∷Γ
          a/ρ≃a/σ∈k/ρ   = Tp∈-/≃ a∈k ρ↑≃σ↑∈j∷Γ
          k/ρ-kd        = kd-/ k-kd ρ↑∈j∷Γ
          k/σ-kd        = kd-/ k-kd σ↑∈j∷Γ
          k/ρ≅k/σ       = kd-/≃ k-kd ρ↑≃σ↑∈j∷Γ
          Λja/ρ∈Πjk/ρ   = ∈-Π-i j/ρ-kd a/ρ∈k/ρ k/ρ-kd
          Πjk/σ<∷Πjk/ρ  = <∷-Π (≅⇒<∷ j/ρ≅j/σ) (≅⇒<∷ (≅-sym k/ρ≅k/σ))
                               (kd-Π j/σ-kd k/σ-kd)
          Λja/σ∈Πjk/ρ   = ∈-⇑ (∈-Π-i j/σ-kd a/σ∈k/σ k/σ-kd) Πjk/σ<∷Πjk/ρ
      in ≃-λ a/ρ≃a/σ∈k/ρ Λja/ρ∈Πjk/ρ Λja/σ∈Πjk/ρ
    Tp∈-/≃ (∈-Π-e {k = k} a∈Πjk b∈j k-kd k[b]-kd) ρ≃σ∈Γ =
      let k[b]/ρ≡k/ρ[b/ρ] = Kind/-sub-↑ k _ _
          k[b]/σ≡k/σ[b/σ] = Kind/-sub-↑ k _ _
          ρ∈Γ , σ∈Γ , _ = ρ≃σ∈Γ
          j-wf          = wf-∷₁ (kd-ctx k-kd)
          j-kd          = wf-kd-inv j-wf
          j/σ≅j/ρ       = kd-/≃-≅ k-kd (≃′-sym ρ≃σ∈Γ)
          j/σ-kd        = kd-/ j-kd σ∈Γ
          ρ↑≃σ↑∈j∷Γ     = ≃′-↑ j/σ-kd j/σ≅j/ρ (≅-refl j/σ-kd) ρ≃σ∈Γ
          ρ↑∈j∷Γ        = (∈-↑ (wf-/ j-wf ρ∈Γ) ρ∈Γ)
          a/ρ≃a/σ∈Πjk/ρ = Tp∈-/≃ a∈Πjk ρ≃σ∈Γ
          b/ρ≃b/σ∈j/ρ   = Tp∈-/≃ b∈j   ρ≃σ∈Γ
          k[b]/ρ≅k[b]/σ = kd-/≃ k[b]-kd ρ≃σ∈Γ
          k/ρ≅k/σ       = kd-/≃ k-kd ρ↑≃σ↑∈j∷Γ
          b/ρ∈j/ρ   = Tp∈-/ b∈j ρ∈Γ
          b/σ∈j/σ   = Tp∈-/ b∈j σ∈Γ
          b/σ∈j/ρ   = ∈-⇑ b/σ∈j/σ (≅⇒<∷ j/σ≅j/ρ)
          k/ρ-kd    = kd-/ k-kd ρ↑∈j∷Γ
          k/ρ[b/ρ]-kd = subst (_ ⊢_kd) k[b]/ρ≡k/ρ[b/ρ] (kd-/ k[b]-kd ρ∈Γ)
          k/ρ[b/σ]-kd = kd-[] k/ρ-kd (∈-tp b/σ∈j/ρ)
          k/ρ[b/σ]≅k[b]/σ = subst (_ ⊢ (k Kind/ _) Kind[ _ ] ≅_)
                                  (sym k[b]/σ≡k/σ[b/σ])
                                  (≅-[] k/ρ≅k/σ (∈-tp b/σ∈j/σ))
          k[b]/σ≅k/ρ[b/ρ] = subst (_ ⊢ _ ≅_) k[b]/ρ≡k/ρ[b/ρ]
                                  (≅-sym k[b]/ρ≅k[b]/σ)
          k/ρ[b/σ]≅k/ρ[b/ρ] = ≅-trans k/ρ[b/σ]≅k[b]/σ k[b]/σ≅k/ρ[b/ρ]
      in (subst (_ ⊢ _ ≃ _ ∈_) (sym k[b]/ρ≡k/ρ[b/ρ])
                (<:-antisym (<:-· (≃⇒<: a/ρ≃a/σ∈Πjk/ρ) b/ρ≃b/σ∈j/ρ b/ρ∈j/ρ
                                  k/ρ-kd k/ρ[b/ρ]-kd)
                            (<:-⇑ (<:-· (≃⇒<: (≃-sym a/ρ≃a/σ∈Πjk/ρ))
                                        (≃-sym b/ρ≃b/σ∈j/ρ) b/σ∈j/ρ k/ρ-kd
                                        k/ρ[b/σ]-kd)
                                  (≅⇒<∷ k/ρ[b/σ]≅k/ρ[b/ρ]))))
    Tp∈-/≃ (∈-s-i a∈b⋯d) ρ≃σ∈Γ =
      let ρ∈Γ , σ∈Γ , _    = ρ≃σ∈Γ
          a/ρ∈*            = Tp∈-⋯-* (Tp∈-/ a∈b⋯d ρ∈Γ)
          a/σ∈*            = Tp∈-⋯-* (Tp∈-/ a∈b⋯d σ∈Γ)
          a/ρ≃a/σ∈b/ρ⋯d/ρ  = Tp∈-/≃ a∈b⋯d ρ≃σ∈Γ
          a/ρ<:a/σ∈a/ρ⋯a/σ = <:-⋯-i (≃⇒<: a/ρ≃a/σ∈b/ρ⋯d/ρ)
          a/σ<:a/ρ∈a/σ⋯a/ρ = <:-⋯-i (≃⇒<: (≃-sym a/ρ≃a/σ∈b/ρ⋯d/ρ))
          a/ρ<:a/σ∈*       = <:-⇑ a/ρ<:a/σ∈a/ρ⋯a/σ
                                  (<∷-⋯ (<:-⊥ a/ρ∈*) (<:-⊤ a/σ∈*))
          a/σ<:a/ρ∈*       = <:-⇑ a/σ<:a/ρ∈a/σ⋯a/ρ
                                  (<∷-⋯ (<:-⊥ a/σ∈*) (<:-⊤ a/ρ∈*))
          a/ρ<:a/σ∈a/ρ⋯a/ρ = <:-⇑ a/ρ<:a/σ∈a/ρ⋯a/σ
                                  (<∷-⋯ (<:-refl a/ρ∈*) a/σ<:a/ρ∈*)
          a/σ<:a/ρ∈a/ρ⋯a/ρ = <:-⇑ a/σ<:a/ρ∈a/σ⋯a/ρ
                                  (<∷-⋯ a/ρ<:a/σ∈* (<:-refl a/ρ∈*))
      in <:-antisym a/ρ<:a/σ∈a/ρ⋯a/ρ a/σ<:a/ρ∈a/ρ⋯a/ρ
    Tp∈-/≃ (∈-⇑ a∈j j<∷k) ρ≃σ∈Γ =
      ≃-⇑ (Tp∈-/≃ a∈j ρ≃σ∈Γ) (<∷-/ j<∷k (proj₁ ρ≃σ∈Γ))

    -- Helpers (to satisfy the termination checker).

    kd-/≃-≅ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {j k ρ σ} →
              kd j ∷ Γ ⊢ k kd → Δ ⊢/ ρ ≃′ σ ∈ Γ → Δ ⊢ j Kind/ ρ ≅ j Kind/ σ
    kd-/≃-≅ (kd-⋯ a∈* _)  ρ≃σ∈Γ = Tp∈-/≃-≅ a∈* ρ≃σ∈Γ
    kd-/≃-≅ (kd-Π j-kd _) ρ≃σ∈Γ = kd-/≃-≅ j-kd ρ≃σ∈Γ

    Tp∈-/≃-≅ : ∀ {m n} {Γ : Ctx m} {Δ : Ctx n} {a j k ρ σ} →
               kd j ∷ Γ ⊢Tp a ∈ k → Δ ⊢/ ρ ≃′ σ ∈ Γ → Δ ⊢ j Kind/ ρ ≅ j Kind/ σ
    Tp∈-/≃-≅ (∈-var x (wf-kd k-kd ∷ Γ-ctx) _) ρ≃σ∈Γ = kd-/≃ k-kd ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-⊥-f (wf-kd k-kd ∷ Γ-ctx))     ρ≃σ∈Γ = kd-/≃ k-kd ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-⊤-f (wf-kd k-kd ∷ Γ-ctx))     ρ≃σ∈Γ = kd-/≃ k-kd ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-∀-f k-kd _)                   ρ≃σ∈Γ = kd-/≃-≅ k-kd ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-→-f a∈* _)                    ρ≃σ∈Γ = Tp∈-/≃-≅ a∈* ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-Π-i j-kd _ _)                 ρ≃σ∈Γ = kd-/≃-≅ j-kd ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-Π-e a∈Πjk _ _ _)              ρ≃σ∈Γ = Tp∈-/≃-≅ a∈Πjk ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-s-i a∈b⋯c)                    ρ≃σ∈Γ = Tp∈-/≃-≅ a∈b⋯c ρ≃σ∈Γ
    Tp∈-/≃-≅ (∈-⇑ a∈k _)                      ρ≃σ∈Γ = Tp∈-/≃-≅ a∈k ρ≃σ∈Γ

  -- Functionality of kind formation and kinding.

  -- Equal single-variable substitutions map well-formed kinds to kind
  -- equations (weak version).
  kd-[≃′] : ∀ {n} {Γ : Ctx n} {a b j k} →
            kd j ∷ Γ ⊢ k kd → Γ ⊢Tp a ∈ j → Γ ⊢Tp b ∈ j → Γ ⊢ a ≃ b ∈ j →
            Γ ⊢ k Kind[ a ] ≅ k Kind[ b ]
  kd-[≃′] k-kd a∈j b∈j a≃b∈j =
    kd-/≃ k-kd (∈-sub (∈-tp a∈j) , ∈-sub (∈-tp b∈j) ,
                ∼∈-sub (≃-tp a≃b∈j) , ∼∈-sub (≃-tp (≃-sym (a≃b∈j))))

  -- Equal single-variable substitutions map well-kinded types to type
  -- equations (weak version).
  Tp∈-[≃′] : ∀ {n} {Γ : Ctx n} {a b c j k} →
             kd j ∷ Γ ⊢Tp a ∈ k → Γ ⊢Tp b ∈ j → Γ ⊢Tp c ∈ j → Γ ⊢ b ≃ c ∈ j →
             Γ ⊢ a [ b ] ≃ a [ c ] ∈ k Kind[ b ]
  Tp∈-[≃′] a∈k b∈j c∈j b≃c∈j =
    Tp∈-/≃ a∈k (∈-sub (∈-tp b∈j) , ∈-sub (∈-tp c∈j) ,
                ∼∈-sub (≃-tp b≃c∈j) , ∼∈-sub (≃-tp (≃-sym (b≃c∈j))))


{-# OPTIONS --safe --experimental-lossy-unification #-}
module Cubical.Homotopy.Group.S3 where

{-
This file contains a summary of what remains for π₄S³≅ℤ/2 to be proved.
See the module π₄S³ at the end of this file.
-}

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Pointed
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function

open import Cubical.Data.Nat
open import Cubical.Data.Sum
open import Cubical.Data.Sigma
open import Cubical.Data.Int
  renaming (_·_ to _·ℤ_ ; _+_ to _+ℤ_)

open import Cubical.Homotopy.Group.Base
open import Cubical.Homotopy.HopfInvariant.Base
open import Cubical.Homotopy.HopfInvariant.Homomorphism
open import Cubical.Homotopy.HopfInvariant.HopfMap
open import Cubical.Homotopy.Whitehead
open import Cubical.Algebra.Group.Instances.IntMod
open import Cubical.Foundations.Isomorphism

open import Cubical.HITs.Sn
open import Cubical.HITs.SetTruncation

open import Cubical.Algebra.Group
  renaming (ℤ to ℤGroup ; Bool to BoolGroup ; Unit to UnitGroup)
open import Cubical.Algebra.Group.ZAction

[_]× : ∀ {ℓ} {X : Pointed ℓ} {n m : ℕ}
  → π' (suc n) X × π' (suc m) X → π' (suc (n + m)) X
[_]× (f , g) = [ f ∣ g ]π'

-- Some type abbreviations (unproved results)
π₃S²-gen : Type
π₃S²-gen = gen₁-by (π'Gr 2 (S₊∙ 2)) ∣ HopfMap ∣₂

π₄S³≅ℤ/something : GroupEquiv ℤGroup (π'Gr 2 (S₊∙ 2))
                 → Type
π₄S³≅ℤ/something eq =
  GroupIso (π'Gr 3 (S₊∙ 3))
           (ℤ/ abs (invEq (fst eq)
             [ ∣ idfun∙ _ ∣₂ , ∣ idfun∙ _ ∣₂ ]×))

miniLem₁ : Type
miniLem₁ = (g : ℤ) → gen₁-by ℤGroup g → (g ≡ 1) ⊎ (g ≡ -1)

miniLem₂ : Type
miniLem₂ = (ϕ : GroupEquiv ℤGroup ℤGroup) (g : ℤ)
      → (abs g ≡ abs (fst (fst ϕ) g))

-- some minor group lemmas
groupLem-help : miniLem₁ → (g : ℤ) →
      gen₁-by ℤGroup g →
      (ϕ : GroupHom ℤGroup ℤGroup) →
      (fst ϕ g ≡ pos 1) ⊎ (fst ϕ g ≡ negsuc 0)
    → isEquiv (fst ϕ)
groupLem-help grlem1 g gen ϕ = main (grlem1 g gen)
  where
  isEquiv- : isEquiv (-_)
  isEquiv- = isoToIsEquiv (iso -_ -_ -Involutive -Involutive)

  lem : fst ϕ (pos 1) ≡ pos 1 → fst ϕ ≡ idfun _
  lem p = funExt lem2
    where
    lem₁ : (x₁ : ℕ) → fst ϕ (pos x₁) ≡ idfun ℤ (pos x₁)
    lem₁ zero = IsGroupHom.pres1 (snd ϕ)
    lem₁ (suc zero) = p
    lem₁ (suc (suc n)) =
      IsGroupHom.pres· (snd ϕ) (pos (suc n)) 1
                             ∙ cong₂ _+ℤ_ (lem₁ (suc n)) p

    lem2 : (x₁ : ℤ) → fst ϕ x₁ ≡ idfun ℤ x₁
    lem2 (pos n) = lem₁ n
    lem2 (negsuc zero) =
      IsGroupHom.presinv (snd ϕ) 1 ∙ cong (λ x → pos 0 - x) p
    lem2 (negsuc (suc n)) =
        (cong (fst ϕ) (sym (+Comm (pos 0) (negsuc (suc n))))
      ∙ IsGroupHom.presinv (snd ϕ) (pos (suc (suc n))))
      ∙∙ +Comm (pos 0) _
      ∙∙ cong (-_) (lem₁ (suc (suc n)))

  lem₂ : fst ϕ (negsuc 0) ≡ pos 1 → fst ϕ ≡ -_
  lem₂ p = funExt lem2
    where
    s = IsGroupHom.presinv (snd ϕ) (negsuc 0)
     ∙∙ +Comm (pos 0) _
     ∙∙ cong -_ p

    lem2 : (n : ℤ) → fst ϕ n ≡ - n
    lem2 (pos zero) = IsGroupHom.pres1 (snd ϕ)
    lem2 (pos (suc zero)) = s
    lem2 (pos (suc (suc n))) =
         IsGroupHom.pres· (snd ϕ) (pos (suc n)) 1
       ∙ cong₂ _+ℤ_ (lem2 (pos (suc n))) s
    lem2 (negsuc zero) = p
    lem2 (negsuc (suc n)) =
         IsGroupHom.pres· (snd ϕ) (negsuc n) (negsuc 0)
       ∙ cong₂ _+ℤ_ (lem2 (negsuc n)) p

  main : (g ≡ pos 1) ⊎ (g ≡ negsuc 0)
      → (fst ϕ g ≡ pos 1) ⊎ (fst ϕ g ≡ negsuc 0)
      → isEquiv (fst ϕ)
  main (inl p) =
    J (λ g p → (fst ϕ g ≡ pos 1)
             ⊎ (fst ϕ g ≡ negsuc 0) → isEquiv (fst ϕ))
      (λ { (inl x) → subst isEquiv (sym (lem x)) (snd (idEquiv _))
         ; (inr x) → subst isEquiv
                            (sym (lem₂ (IsGroupHom.presinv (snd ϕ) (pos 1)
                          ∙ (cong (λ x → pos 0 - x) x))))
                            isEquiv- })
                 (sym p)
  main (inr p) =
    J (λ g p → (fst ϕ g ≡ pos 1)
             ⊎ (fst ϕ g ≡ negsuc 0) → isEquiv (fst ϕ))
      (λ { (inl x) → subst isEquiv (sym (lem₂ x)) isEquiv-
         ; (inr x) → subst isEquiv
                       (sym (lem (
                       IsGroupHom.presinv (snd ϕ) (negsuc 0)
                     ∙ cong (λ x → pos 0 - x) x)))
                    (snd (idEquiv _))})
      (sym p)

groupLem : {G : Group₀}
         → miniLem₁
         → GroupEquiv ℤGroup G
         → (g : fst G)
         → gen₁-by G g
         → (ϕ : GroupHom G ℤGroup)
         → (fst ϕ g ≡ 1) ⊎ (fst ϕ g ≡ -1)
         → isEquiv (fst ϕ)
groupLem {G = G} s =
  GroupEquivJ
    (λ G _ → (g : fst G)
         → gen₁-by G g
         → (ϕ : GroupHom G ℤGroup)
         → (fst ϕ g ≡ 1) ⊎ (fst ϕ g ≡ -1)
         → isEquiv (fst ϕ))
    (groupLem-help s)

-- summary
module π₄S³
  (mini-lem₁ : miniLem₁)
  (mini-lem₂ : miniLem₂)
  (ℤ≅π₃S² : GroupEquiv ℤGroup (π'Gr 2 (S₊∙ 2)))
  (gen-by-HopfMap : π₃S²-gen)
  (π₄S³≅ℤ/whitehead : π₄S³≅ℤ/something ℤ≅π₃S²)
  (hopfWhitehead :
       abs (HopfInvariant-π' 0
             ([ (∣ idfun∙ _ ∣₂ , ∣ idfun∙ _ ∣₂) ]×))
     ≡ 2)
  where
  π₄S³ = π'Gr 3 (S₊∙ 3)

  hopfInvariantEquiv : GroupEquiv (π'Gr 2 (S₊∙ 2)) ℤGroup
  fst (fst hopfInvariantEquiv) = HopfInvariant-π' 0
  snd (fst hopfInvariantEquiv) =
    groupLem mini-lem₁ ℤ≅π₃S² ∣ HopfMap ∣₂
             gen-by-HopfMap
             (GroupHom-HopfInvariant-π' 0)
             (abs→⊎ _ _ HopfInvariant-HopfMap)
  snd hopfInvariantEquiv = snd (GroupHom-HopfInvariant-π' 0)

  lem : ∀ {G : Group₀} (ϕ ψ : GroupEquiv ℤGroup G) (g : fst G)
      → abs (invEq (fst ϕ) g) ≡ abs (invEq (fst ψ) g)
  lem =
    GroupEquivJ
      (λ G ϕ → (ψ : GroupEquiv ℤGroup G) (g : fst G)
      → abs (invEq (fst ϕ) g) ≡ abs (invEq (fst ψ) g))
      λ ψ → mini-lem₂ (invGroupEquiv ψ)

  main : GroupIso π₄S³ (ℤ/ 2)
  main = subst (GroupIso π₄S³)
               (cong (ℤ/_) (lem ℤ≅π₃S² (invGroupEquiv (hopfInvariantEquiv)) _
                               ∙ hopfWhitehead))
               π₄S³≅ℤ/whitehead


------------------------------------------------------------------------
-- A language of parser equivalence proofs
------------------------------------------------------------------------

-- This module defines yet another set of equivalence relations and
-- preorders for parsers. For symmetric kinds these relations are
-- equalities (compatible equivalence relations) by construction, and
-- they are sound and complete with respect to the previously defined
-- equivalences (see TotalParserCombinators.Congruence.Sound for the
-- soundness proof). This means that parser and language equivalence
-- are also equalities. The related orderings are compatible
-- preorders.

module TotalParserCombinators.Congruence where

open import Codata.Musical.Notation
open import Data.List
open import Data.List.Relation.Binary.BagAndSetEquality
  using (bag) renaming (_∼[_]_ to _List-∼[_]_)
open import Data.Maybe hiding (_>>=_)
open import Data.Nat hiding (_^_)
open import Data.Product
open import Data.Vec.Recursive
open import Function
open import Function.Related using (Symmetric-kind; ⌊_⌋)
open import Relation.Binary.PropositionalEquality using (_≡_; _≗_)

open import TotalParserCombinators.Derivative using (D)
open import TotalParserCombinators.CoinductiveEquality as CE
  using (_∼[_]c_; _∷_)
open import TotalParserCombinators.Parser
open import TotalParserCombinators.Semantics
  hiding ([_-_]_⊛_; [_-_]_>>=_)

infixl 50 [_-_]_⊛_ [_-_-_-_]_⊛_ _<$>_
infix  10 [_-_]_>>=_ [_-_-_-_]_>>=_
infixl  5 _∣_
infix   5 _∷_
infix   4 _∼[_]P_ ∞⟨_⟩_∼[_]P_ _≅P_ _≈P_
infix   3 _∎
infixr  2 _∼⟨_⟩_ _≅⟨_⟩_

------------------------------------------------------------------------
-- Helper functions

flatten₁ : {A : Set} → Maybe (Maybe A ^ 2) → Maybe A
flatten₁ nothing        = nothing
flatten₁ (just (m , _)) = m

flatten₂ : {A : Set} → Maybe (Maybe A ^ 2) → Maybe A
flatten₂ nothing        = nothing
flatten₂ (just (_ , m)) = m

------------------------------------------------------------------------
-- Equivalence proof programs

mutual

  _≅P_ : ∀ {Tok R xs₁ xs₂} → Parser Tok R xs₁ → Parser Tok R xs₂ → Set₁
  p₁ ≅P p₂ = p₁ ∼[ parser ]P p₂

  _≈P_ : ∀ {Tok R xs₁ xs₂} → Parser Tok R xs₁ → Parser Tok R xs₂ → Set₁
  p₁ ≈P p₂ = p₁ ∼[ language ]P p₂

  data _∼[_]P_ {Tok} :
         ∀ {R xs₁ xs₂} →
         Parser Tok R xs₁ → Kind → Parser Tok R xs₂ → Set₁ where

    -- This constructor, which corresponds to CE._∷_, ensures that the
    -- relation is complete.

    _∷_ : ∀ {k R xs₁ xs₂}
            {p₁ : Parser Tok R xs₁}
            {p₂ : Parser Tok R xs₂}
          (xs₁≈xs₂ : xs₁ List-∼[ k ] xs₂)
          (Dp₁≈Dp₂ : ∀ t → ∞ (D t p₁ ∼[ k ]P D t p₂)) →
          p₁ ∼[ k ]P p₂

    -- Equational reasoning.

    _∎     : ∀ {k R xs} (p : Parser Tok R xs) → p ∼[ k ]P p

    _∼⟨_⟩_ : ∀ {k R xs₁ xs₂ xs₃}
               (p₁ : Parser Tok R xs₁)
               {p₂ : Parser Tok R xs₂}
               {p₃ : Parser Tok R xs₃}
             (p₁≈p₂ : p₁ ∼[ k ]P p₂) (p₂≈p₃ : p₂ ∼[ k ]P p₃) →
             p₁ ∼[ k ]P p₃

    _≅⟨_⟩_ : ∀ {k R xs₁ xs₂ xs₃}
               (p₁ : Parser Tok R xs₁)
               {p₂ : Parser Tok R xs₂}
               {p₃ : Parser Tok R xs₃}
             (p₁≅p₂ : p₁ ≅P p₂) (p₂≈p₃ : p₂ ∼[ k ]P p₃) →
             p₁ ∼[ k ]P p₃

    sym : ∀ {k : Symmetric-kind} {R xs₁ xs₂}
            {p₁ : Parser Tok R xs₁}
            {p₂ : Parser Tok R xs₂}
          (p₁≈p₂ : p₁ ∼[ ⌊ k ⌋ ]P p₂) → p₂ ∼[ ⌊ k ⌋ ]P p₁

    -- Congruences.

    return : ∀ {k R} {x₁ x₂ : R}
             (x₁≡x₂ : x₁ ≡ x₂) → return x₁ ∼[ k ]P return x₂

    fail : ∀ {k R} → fail {R = R} ∼[ k ]P fail {R = R}

    token : ∀ {k} → token ∼[ k ]P token

    _∣_ : ∀ {k R xs₁ xs₂ xs₃ xs₄}
            {p₁ : Parser Tok R xs₁}
            {p₂ : Parser Tok R xs₂}
            {p₃ : Parser Tok R xs₃}
            {p₄ : Parser Tok R xs₄}
          (p₁≈p₃ : p₁ ∼[ k ]P p₃) (p₂≈p₄ : p₂ ∼[ k ]P p₄) →
          p₁ ∣ p₂ ∼[ k ]P p₃ ∣ p₄

    _<$>_ : ∀ {k R₁ R₂} {f₁ f₂ : R₁ → R₂} {xs₁ xs₂}
              {p₁ : Parser Tok R₁ xs₁}
              {p₂ : Parser Tok R₁ xs₂}
            (f₁≗f₂ : f₁ ≗ f₂) (p₁≈p₂ : p₁ ∼[ k ]P p₂) →
            f₁ <$> p₁ ∼[ k ]P f₂ <$> p₂

    [_-_]_⊛_ :
      ∀ {k R₁ R₂} xs₁xs₂ fs₁fs₂ →
      let xs₁ = flatten₁ xs₁xs₂; xs₂ = flatten₂ xs₁xs₂
          fs₁ = flatten₁ fs₁fs₂; fs₂ = flatten₂ fs₁fs₂ in
        {p₁ : ∞⟨ xs₁ ⟩Parser Tok (R₁ → R₂) (flatten fs₁)}
        {p₂ : ∞⟨ fs₁ ⟩Parser Tok  R₁       (flatten xs₁)}
        {p₃ : ∞⟨ xs₂ ⟩Parser Tok (R₁ → R₂) (flatten fs₂)}
        {p₄ : ∞⟨ fs₂ ⟩Parser Tok  R₁       (flatten xs₂)}
      (p₁≈p₃ : ∞⟨ xs₁xs₂ ⟩ p₁ ∼[ k ]P p₃)
      (p₂≈p₄ : ∞⟨ fs₁fs₂ ⟩ p₂ ∼[ k ]P p₄) →
      p₁ ⊛ p₂ ∼[ k ]P p₃ ⊛ p₄

    [_-_]_>>=_ :
      ∀ {k R₁ R₂} (f₁f₂ : Maybe (Maybe (R₁ → List R₂) ^ 2)) xs₁xs₂ →
      let f₁  = flatten₁ f₁f₂;   f₂  = flatten₂ f₁f₂
          xs₁ = flatten₁ xs₁xs₂; xs₂ = flatten₂ xs₁xs₂ in
        {p₁ : ∞⟨ f₁ ⟩Parser Tok R₁ (flatten xs₁)}
        {p₂ : (x : R₁) → ∞⟨ xs₁ ⟩Parser Tok R₂ (apply f₁ x)}
        {p₃ : ∞⟨ f₂ ⟩Parser Tok R₁ (flatten xs₂)}
        {p₄ : (x : R₁) → ∞⟨ xs₂ ⟩Parser Tok R₂ (apply f₂ x)}
      (p₁≈p₃ : ∞⟨ f₁f₂ ⟩ p₁ ∼[ k ]P p₃)
      (p₂≈p₄ : ∀ x → ∞⟨ xs₁xs₂ ⟩ p₂ x ∼[ k ]P p₄ x) →
      p₁ >>= p₂ ∼[ k ]P p₃ >>= p₄

    nonempty : ∀ {k R xs₁ xs₂}
                 {p₁ : Parser Tok R xs₁} {p₂ : Parser Tok R xs₂}
               (p₁≈p₂ : p₁ ∼[ k ]P p₂) → nonempty p₁ ∼[ k ]P nonempty p₂

    cast : ∀ {k R xs₁ xs₂ xs₁′ xs₂′}
             {p₁ : Parser Tok R xs₁} {p₂ : Parser Tok R xs₂}
             {xs₁≈xs₁′ : xs₁ List-∼[ bag ] xs₁′}
             {xs₂≈xs₂′ : xs₂ List-∼[ bag ] xs₂′}
           (p₁≈p₂ : p₁ ∼[ k ]P p₂) →
           cast xs₁≈xs₁′ p₁ ∼[ k ]P cast xs₂≈xs₂′ p₂

  -- Certain proofs can be coinductive if both sides are delayed.

  ∞⟨_⟩_∼[_]P_ :
    ∀ {Tok R xs₁ xs₂} {A : Set} (m₁m₂ : Maybe (Maybe A ^ 2)) →
    ∞⟨ flatten₁ m₁m₂ ⟩Parser Tok R xs₁ → Kind →
    ∞⟨ flatten₂ m₁m₂ ⟩Parser Tok R xs₂ → Set₁
  ∞⟨ nothing ⟩ p₁ ∼[ k ]P p₂ = ∞ (♭  p₁ ∼[ k ]P ♭  p₂)
  ∞⟨ just _  ⟩ p₁ ∼[ k ]P p₂ =    ♭? p₁ ∼[ k ]P ♭? p₂

------------------------------------------------------------------------
-- Some derived combinators

[_-_-_-_]_⊛_ :
  ∀ {k Tok R₁ R₂} xs₁ xs₂ fs₁ fs₂
    {p₁ : ∞⟨ xs₁ ⟩Parser Tok (R₁ → R₂) (flatten fs₁)}
    {p₂ : ∞⟨ fs₁ ⟩Parser Tok  R₁       (flatten xs₁)}
    {p₃ : ∞⟨ xs₂ ⟩Parser Tok (R₁ → R₂) (flatten fs₂)}
    {p₄ : ∞⟨ fs₂ ⟩Parser Tok  R₁       (flatten xs₂)} →
  ♭? p₁ ∼[ k ]P ♭? p₃ → ♭? p₂ ∼[ k ]P ♭? p₄ → p₁ ⊛ p₂ ∼[ k ]P p₃ ⊛ p₄
[ xs₁ - xs₂ - fs₁ - fs₂ ] p₁≈p₃ ⊛ p₂≈p₄ =
  [ just (xs₁ , xs₂) - just (fs₁ , fs₂) ] p₁≈p₃ ⊛ p₂≈p₄

[_-_-_-_]_>>=_ :
  ∀ {k Tok R₁ R₂} (f₁ f₂ : Maybe (R₁ → List R₂)) xs₁ xs₂
    {p₁ : ∞⟨ f₁ ⟩Parser Tok R₁ (flatten xs₁)}
    {p₂ : (x : R₁) → ∞⟨ xs₁ ⟩Parser Tok R₂ (apply f₁ x)}
    {p₃ : ∞⟨ f₂ ⟩Parser Tok R₁ (flatten xs₂)}
    {p₄ : (x : R₁) → ∞⟨ xs₂ ⟩Parser Tok R₂ (apply f₂ x)} →
  ♭? p₁ ∼[ k ]P ♭? p₃ → (∀ x → ♭? (p₂ x) ∼[ k ]P ♭? (p₄ x)) →
  p₁ >>= p₂ ∼[ k ]P p₃ >>= p₄
[ f₁ - f₂ - xs₁ - xs₂ ] p₁≈p₃ >>= p₂≈p₄ =
  [ just (f₁ , f₂) - just (xs₁ , xs₂) ] p₁≈p₃ >>= p₂≈p₄

------------------------------------------------------------------------
-- Completeness

complete : ∀ {k Tok R xs₁ xs₂}
             {p₁ : Parser Tok R xs₁}
             {p₂ : Parser Tok R xs₂} →
           p₁ ∼[ k ] p₂ → p₁ ∼[ k ]P p₂
complete = complete′ ∘ CE.complete
  where
  complete′ : ∀ {k Tok R xs₁ xs₂}
                {p₁ : Parser Tok R xs₁}
                {p₂ : Parser Tok R xs₂} →
              p₁ ∼[ k ]c p₂ → p₁ ∼[ k ]P p₂
  complete′ (xs₁≈xs₂ ∷ Dp₁≈Dp₂) =
    xs₁≈xs₂ ∷ λ t → ♯ complete′ (♭ (Dp₁≈Dp₂ t))


open import Prelude
open import Nat
open import contexts
open import dom-eq
open import dynamics-core
open import lemmas-disjointness
open import statics-core

module disjointness where
  -- if a hole name is new in a term, then the resultant context is
  -- disjoint from any singleton context with that hole name
  mutual
    elab-new-disjoint-synth : ∀ {e u τ d Δ Γ Γ' τ'} →
                              hole-name-new e u →
                              Γ ⊢ e ⇒ τ ~> d ⊣ Δ →
                              Δ ## (■ (u , Γ' , τ'))
    elab-new-disjoint-synth HNNum ESNum = empty-disj _
    elab-new-disjoint-synth (HNPlus hn hn₁) (ESPlus apt x x₁ x₂) = disjoint-parts (elab-new-disjoint-ana hn x₁) (elab-new-disjoint-ana hn₁ x₂)
    elab-new-disjoint-synth (HNAsc hn) (ESAsc x) = elab-new-disjoint-ana hn x
    elab-new-disjoint-synth HNVar (ESVar x₁) = empty-disj (■ (_ , _ , _))
    elab-new-disjoint-synth (HNLam2 hn) (ESLam x₁ exp) = elab-new-disjoint-synth hn exp
    elab-new-disjoint-synth (HNHole x) ESEHole = disjoint-singles x
    elab-new-disjoint-synth (HNNEHole x hn) (ESNEHole x₁ exp) = disjoint-parts (disjoint-singles x) (elab-new-disjoint-synth hn exp)
    elab-new-disjoint-synth (HNAp hn hn₁) (ESAp x x₁ x₂ x₃ x₄ x₅) =
                                            disjoint-parts (elab-new-disjoint-ana hn x₄)
                                                  (elab-new-disjoint-ana hn₁ x₅)
    elab-new-disjoint-synth (HNPair hn hn₁) (ESPair x x₁ exp exp₁) = disjoint-parts (elab-new-disjoint-synth hn exp) (elab-new-disjoint-synth hn₁ exp₁)
    elab-new-disjoint-synth (HNLam1 hn) ()
    elab-new-disjoint-synth (HNInl hn) ()
    elab-new-disjoint-synth (HNInr hn) ()
    elab-new-disjoint-synth (HNCase hn hn₁ hn₂) ()
    elab-new-disjoint-synth (HNFst hn) (ESFst x x₁ x₂) = elab-new-disjoint-ana hn x₂
    elab-new-disjoint-synth (HNSnd hn) (ESSnd x x₁ x₂) = elab-new-disjoint-ana hn x₂

    elab-new-disjoint-ana : ∀{e u τ d Δ Γ Γ' τ' τ2} →
                            hole-name-new e u →
                            Γ ⊢ e ⇐ τ ~> d :: τ2 ⊣ Δ →
                            Δ ## (■ (u , Γ' , τ'))
    elab-new-disjoint-ana hn (EASubsume x x₁ x₂ x₃) = elab-new-disjoint-synth hn x₂
    elab-new-disjoint-ana (HNLam1 hn) (EALam x₁ x₂ ex) = elab-new-disjoint-ana hn ex
    elab-new-disjoint-ana (HNHole x) EAEHole = disjoint-singles x
    elab-new-disjoint-ana (HNNEHole x hn) (EANEHole x₁ x₂) = disjoint-parts (disjoint-singles x) (elab-new-disjoint-synth hn x₂)
    elab-new-disjoint-ana (HNInl hn) (EAInl x x₁) = elab-new-disjoint-ana hn x₁
    elab-new-disjoint-ana (HNInr hn) (EAInr x x₁) = elab-new-disjoint-ana hn x₁
    elab-new-disjoint-ana (HNCase hn hn₁ hn₂) (EACase x x₁ x₂ x₃ x₄ x₅ _ _ x₆ x₇ x₈ x₉) = disjoint-parts (elab-new-disjoint-synth hn x₆) (disjoint-parts (elab-new-disjoint-ana hn₁ x₈) (elab-new-disjoint-ana hn₂ x₉))

  -- dual of the above: if elaborating a term produces a context that's
  -- disjoint with a singleton context, it must be that the index is a new
  -- hole name in the original term
  mutual
    elab-disjoint-new-synth : ∀{e τ d Δ u Γ Γ' τ'} →
                              Γ ⊢ e ⇒ τ ~> d ⊣ Δ →
                              Δ ## (■ (u , Γ' , τ')) →
                              hole-name-new e u
    elab-disjoint-new-synth ESNum disj = HNNum
    elab-disjoint-new-synth (ESPlus {Δ1 = Δ1} apt x x₁ x₂) disj
      with elab-disjoint-new-ana x₁ (disjoint-union1 disj) | elab-disjoint-new-ana x₂ (disjoint-union2 {Γ1 = Δ1} disj)
    ... | ih1 | ih2 = HNPlus ih1 ih2
    elab-disjoint-new-synth (ESVar x₁) disj = HNVar
    elab-disjoint-new-synth (ESLam x₁ ex) disj = HNLam2 (elab-disjoint-new-synth ex disj)
    elab-disjoint-new-synth (ESAp {Δ1 = Δ1} x x₁ x₂ x₃ x₄ x₅) disj
      with elab-disjoint-new-ana x₄ (disjoint-union1 disj) | elab-disjoint-new-ana x₅ (disjoint-union2 {Γ1 = Δ1} disj)
    ... | ih1 | ih2 = HNAp ih1 ih2
    elab-disjoint-new-synth {Γ = Γ} ESEHole disj = HNHole (singles-notequal disj)
    elab-disjoint-new-synth {Γ = Γ} (ESNEHole {u = u} {Δ = Δ} x ex) disj = HNNEHole (singles-notequal (disjoint-union1 {Γ2 = Δ} disj)) (elab-disjoint-new-synth ex (disjoint-union2 {Γ1 = ■ (u , Γ , ⦇-⦈)} {Γ2 = Δ} disj))
    elab-disjoint-new-synth (ESAsc x) disj = HNAsc (elab-disjoint-new-ana x disj)
    elab-disjoint-new-synth (ESPair {Δ1 = Δ1} {Δ2 = Δ2} x x₁ x₂ x₃) disj = HNPair (elab-disjoint-new-synth x₂ (disjoint-union1 {Γ1 = Δ1} {Γ2 = Δ2} disj)) (elab-disjoint-new-synth x₃ (disjoint-union2 {Γ1 = Δ1} {Γ2 = Δ2} disj))
    elab-disjoint-new-synth (ESFst x x₁ x₂) disj = HNFst (elab-disjoint-new-ana x₂ disj)
    elab-disjoint-new-synth (ESSnd x x₁ x₂) disj = HNSnd (elab-disjoint-new-ana x₂ disj)

    elab-disjoint-new-ana : ∀{e τ d Δ u Γ Γ' τ2 τ'} →
                            Γ ⊢ e ⇐ τ ~> d :: τ2 ⊣ Δ →
                            Δ ## (■ (u , Γ' , τ')) →
                            hole-name-new e u
    elab-disjoint-new-ana (EALam x₁ x₂ ex) disj = HNLam1 (elab-disjoint-new-ana ex disj)
    elab-disjoint-new-ana (EASubsume x x₁ x₂ x₃) disj = elab-disjoint-new-synth x₂ disj
    elab-disjoint-new-ana EAEHole disj = HNHole (singles-notequal disj)
    elab-disjoint-new-ana {Γ = Γ} (EANEHole {u = u} {τ = τ} {Δ = Δ} x x₁) disj = HNNEHole (singles-notequal (disjoint-union1 {Γ2 = Δ} disj)) (elab-disjoint-new-synth x₁ (disjoint-union2 {Γ1 = ■ (u , Γ , τ)} {Γ2 = Δ} disj))
    elab-disjoint-new-ana (EAInl x x₁) disj = HNInl (elab-disjoint-new-ana x₁ disj)
    elab-disjoint-new-ana (EAInr x x₁) disj = HNInr (elab-disjoint-new-ana x₁ disj)
    elab-disjoint-new-ana {Γ = Γ} (EACase {Δ = Δ} {Δ1 = Δ1} {Δ2 = Δ2} x x₁ x₂ x₃ x₄ x₅ _ _ x₆ x₇ x₈ x₉) disj = HNCase (elab-disjoint-new-synth x₆ (disjoint-union1 disj)) (elab-disjoint-new-ana x₈ (disjoint-union1 {Γ2 = Δ2} (disjoint-union2 {Γ1 = Δ} disj))) (elab-disjoint-new-ana x₉ (disjoint-union2 {Γ1 = Δ1} (disjoint-union2 {Γ1 = Δ} disj)))

  -- collect up the hole names of a term as the indices of a trivial context
  data holes : (e : hexp) (H : ⊤ ctx) → Set where
    HNum   : ∀{n} → holes (N n) ∅
    HPlus  : ∀{e1 e2 H1 H2} → holes e1 H1 → holes e2 H2 → holes (e1 ·+ e2) (H1 ∪ H2)
    HAsc   : ∀{e τ H} → holes e H → holes (e ·: τ) H
    HVar   : ∀{x} → holes (X x) ∅
    HLam1  : ∀{x e H} → holes e H → holes (·λ x e) H
    HLam2  : ∀{x e τ H} → holes e H → holes (·λ x ·[ τ ] e) H
    HAp    : ∀{e1 e2 H1 H2} → holes e1 H1 → holes e2 H2 → holes (e1 ∘ e2) (H1 ∪ H2)
    HInl   : ∀{e H} → holes e H → holes (inl e) H
    HInr   : ∀{e H} → holes e H → holes (inr e) H
    HCase  : ∀{e x e1 y e2 H H1 H2} → holes e H → holes e1 H1 → holes e2 H2 → holes (case e x e1 y e2) (H ∪ (H1 ∪ H2))
    HPair  : ∀{e1 e2 H1 H2} → holes e1 H1 → holes e2 H2 → holes ⟨ e1 , e2 ⟩ (H1 ∪ H2)
    HFst   : ∀{e H} → holes e H → holes (fst e) H
    HSnd   : ∀{e H} → holes e H → holes (snd e) H
    HEHole : ∀{u} → holes (⦇-⦈[ u ]) (■ (u , <>))
    HNEHole : ∀{e u H} → holes e H → holes (⦇⌜ e ⌟⦈[ u ]) (H ,, (u , <>))

  -- the above judgement has mode (∀,∃). this doesn't prove uniqueness; any
  -- context that extends the one computed here will be indistinguishable
  -- but we'll treat this one as canonical
  find-holes : (e : hexp) → Σ[ H ∈ ⊤ ctx ](holes e H)
  find-holes (N n) = ∅ , HNum
  find-holes (e1 ·+ e2) with find-holes e1 | find-holes e2
  ... | (h1 , d1) | (h2 , d2)  = (h1 ∪ h2 ) , (HPlus d1 d2)
  find-holes (e ·: x) with find-holes e
  ... | (h , d) = h , (HAsc d)
  find-holes (X x) = ∅ , HVar
  find-holes (·λ x e) with find-holes e
  ... | (h , d) = h , HLam1 d
  find-holes (·λ x ·[ x₁ ] e) with find-holes e
  ... | (h , d) = h , HLam2 d
  find-holes (e1 ∘ e2) with find-holes e1 | find-holes e2
  ... | (h1 , d1) | (h2 , d2)  = (h1 ∪ h2 ) , (HAp d1 d2)
  find-holes ⦇-⦈[ x ] = (■ (x , <>)) , HEHole
  find-holes ⦇⌜ e ⌟⦈[ x ] with find-holes e
  ... | (h , d) = h ,, (x , <>) , HNEHole d
  find-holes (inl e) with find-holes e
  ... | (h , d) = h , HInl d
  find-holes (inr e) with find-holes e
  ... | (h , d) = h , HInr d
  find-holes (case e x e₁ x₁ e₂) with find-holes e | find-holes e₁ | find-holes e₂
  ... | (h , d) | (h1 , d1) | (h2 , d2) = (h ∪ (h1 ∪ h2)) , HCase d d1 d2
  find-holes ⟨ e , e₁ ⟩ with find-holes e | find-holes e₁
  ... | (h , d) | (h1 , d1) = h ∪ h1 , HPair d d1
  find-holes (fst e) with find-holes e
  ... | (h , d) = h , HFst d
  find-holes (snd e) with find-holes e
  ... | (h , d) = h , HSnd d

  -- if a hole name is new then it's apart from the collection of hole
  -- names
  lem-apart-new : ∀{e H u} → holes e H → hole-name-new e u → u # H
  lem-apart-new HNum x = refl
  lem-apart-new (HPlus {H1 = H1} {H2 = H2} h h₁) (HNPlus hn hn₁) = apart-parts H1 H2 _ (lem-apart-new h hn) (lem-apart-new h₁ hn₁)
  lem-apart-new (HAsc h) (HNAsc hn) = lem-apart-new h hn
  lem-apart-new HVar HNVar = refl
  lem-apart-new (HLam1 h) (HNLam1 hn) = lem-apart-new h hn
  lem-apart-new (HLam2 h) (HNLam2 hn) = lem-apart-new h hn
  lem-apart-new (HAp {H1 = H1} {H2 = H2} h h₁) (HNAp hn hn₁) = apart-parts H1 H2 _ (lem-apart-new h hn) (lem-apart-new h₁ hn₁)
  lem-apart-new HEHole (HNHole x) = apart-singleton (flip x)
  lem-apart-new (HNEHole {u = u'} {H = H} h) (HNNEHole  {u = u}  x hn) = apart-parts (■ (u' , <>)) H u (apart-singleton (flip x)) (lem-apart-new h hn)
  lem-apart-new (HInl h) (HNInl hn) = lem-apart-new h hn
  lem-apart-new (HInr h) (HNInr hn) = lem-apart-new h hn
  lem-apart-new (HCase {H = H} {H1 = H1} {H2 = H2} h h₁ h₂) (HNCase {u = u'} hn hn₁ hn₂) = apart-parts H (H1 ∪ H2) u' (lem-apart-new h hn) (apart-parts H1 H2 u' (lem-apart-new h₁ hn₁) (lem-apart-new h₂ hn₂))
  lem-apart-new (HPair {H1 = H1} {H2 = H2} h h₁) (HNPair {u = u} hn hn₁) = apart-parts H1 H2 u (lem-apart-new h hn) (lem-apart-new h₁ hn₁)
  lem-apart-new (HFst h) (HNFst hn) = lem-apart-new h hn
  lem-apart-new (HSnd h) (HNSnd hn) = lem-apart-new h hn

  -- if the holes of two expressions are disjoint, so are their collections
  -- of hole names
  holes-disjoint-disjoint : ∀{e1 e2 H1 H2} →
                            holes e1 H1 →
                            holes e2 H2 →
                            holes-disjoint e1 e2 →
                            H1 ## H2
  holes-disjoint-disjoint HNum he2 HDNum = empty-disj _
  holes-disjoint-disjoint (HPlus he1 he2) he3 (HDPlus hd hd₁) = disjoint-parts (holes-disjoint-disjoint he1 he3 hd) (holes-disjoint-disjoint he2 he3 hd₁)
  holes-disjoint-disjoint (HAsc he1) he2 (HDAsc hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint HVar he2 HDVar = empty-disj _
  holes-disjoint-disjoint (HLam1 he1) he2 (HDLam1 hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint (HLam2 he1) he2 (HDLam2 hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint (HAp he1 he2) he3 (HDAp hd hd₁) = disjoint-parts (holes-disjoint-disjoint he1 he3 hd) (holes-disjoint-disjoint he2 he3 hd₁)
  holes-disjoint-disjoint HEHole he2 (HDHole x) = lem-apart-sing-disj (lem-apart-new he2 x)
  holes-disjoint-disjoint (HNEHole he1) he2 (HDNEHole x hd) = disjoint-parts (lem-apart-sing-disj (lem-apart-new he2 x)) (holes-disjoint-disjoint he1 he2 hd)
  holes-disjoint-disjoint (HInl he1) he2 (HDInl hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint (HInr he1) he2 (HDInr hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint (HCase he1 he3 he4) he2 (HDCase hd hd₁ hd₂) = disjoint-parts (holes-disjoint-disjoint he1 he2 hd) (disjoint-parts (holes-disjoint-disjoint he3 he2 hd₁) (holes-disjoint-disjoint he4 he2 hd₂))
  holes-disjoint-disjoint (HPair he1 he3) he2 (HDPair hd hd₁) = disjoint-parts (holes-disjoint-disjoint he1 he2 hd) (holes-disjoint-disjoint he3 he2 hd₁)
  holes-disjoint-disjoint (HFst he1) he2 (HDFst hd) = holes-disjoint-disjoint he1 he2 hd
  holes-disjoint-disjoint (HSnd he1) he2 (HDSnd hd) = holes-disjoint-disjoint he1 he2 hd

  -- the holes of an expression have the same domain as the context
  -- produced during expansion; that is, we don't add anything we don't
  -- find in the term during expansion.
  mutual
    holes-delta-ana : ∀{Γ H e τ d τ' Δ} →
                      holes e H →
                      Γ ⊢ e ⇐ τ ~> d :: τ' ⊣ Δ →
                      dom-eq Δ H
    holes-delta-ana (HLam1 h) (EALam x₁ x₂ exp) = holes-delta-ana h exp
    holes-delta-ana h (EASubsume x x₁ x₂ x₃) = holes-delta-synth h x₂
    holes-delta-ana (HEHole {u = u}) EAEHole = dom-single u
    holes-delta-ana (HNEHole {u = u} h) (EANEHole x x₁) = dom-union (lem-apart-sing-disj (lem-apart-new h (elab-disjoint-new-synth x₁ x))) (dom-single u) (holes-delta-synth h x₁)
    holes-delta-ana (HInl h) (EAInl x x₁) = holes-delta-ana h x₁
    holes-delta-ana (HInr h) (EAInr x x₁) = holes-delta-ana h x₁
    holes-delta-ana (HCase h h₁ h₂) (EACase {Δ = Δ} x x₁ x₂ x₃ x₄ x₅ _ _ x₆ x₇ x₈ x₉) = dom-union (##-comm (disjoint-parts (##-comm (holes-disjoint-disjoint h h₁ x)) (##-comm (holes-disjoint-disjoint h h₂ x₁)))) (holes-delta-synth h x₆) (dom-union (holes-disjoint-disjoint h₁ h₂ x₂) (holes-delta-ana h₁ x₈) (holes-delta-ana h₂ x₉))

    holes-delta-synth : ∀{Γ H e τ d Δ} →
                        holes e H →
                        Γ ⊢ e ⇒ τ ~> d ⊣ Δ →
                        dom-eq Δ H
    holes-delta-synth HNum ESNum = dom-∅
    holes-delta-synth (HPlus h h₁) (ESPlus x apt x₁ x₂) = dom-union (holes-disjoint-disjoint h h₁ x) (holes-delta-ana h x₁) (holes-delta-ana h₁ x₂)
    holes-delta-synth (HAsc h) (ESAsc x) = holes-delta-ana h x
    holes-delta-synth HVar (ESVar x₁) = dom-∅
    holes-delta-synth (HLam2 h) (ESLam x₁ exp) = holes-delta-synth h exp
    holes-delta-synth (HAp h h₁) (ESAp x x₁ x₂ x₃ x₄ x₅) = dom-union (holes-disjoint-disjoint h h₁ x) (holes-delta-ana h x₄) (holes-delta-ana h₁ x₅)
    holes-delta-synth (HEHole {u = u}) ESEHole = dom-single u
    holes-delta-synth (HNEHole {u = u} h) (ESNEHole x exp) = dom-union (lem-apart-sing-disj (lem-apart-new h (elab-disjoint-new-synth exp x))) (dom-single u) (holes-delta-synth h exp)
    holes-delta-synth (HPair h h₁) (ESPair x x₁ exp exp₁) = dom-union (holes-disjoint-disjoint h h₁ x) (holes-delta-synth h exp) (holes-delta-synth h₁ exp₁)
    holes-delta-synth (HLam1 hn) ()
    holes-delta-synth (HInl hn) ()
    holes-delta-synth (HInr hn) ()
    holes-delta-synth (HCase hn hn₁ hn₂) ()
    holes-delta-synth (HFst hn) (ESFst x x₁ x₂) = holes-delta-ana hn x₂
    holes-delta-synth (HSnd hn) (ESSnd x x₁ x₂) = holes-delta-ana hn x₂
  -- this is one of the main results of this file:
  --
  -- if you elaborate two hole-disjoint expressions analytically, the Δs
  -- produced are disjoint.
  --
  -- the proof technique here is explcitly *not* structurally inductive on the
  -- expansion judgement, because that approach relies on weakening of
  -- expansion, which is false because of the substitution contexts. giving
  -- expansion weakning would take away unicity, so we avoid the whole
  -- question.
  elab-ana-disjoint : ∀{e1 e2 τ1 τ2 e1' e2' τ1' τ2' Γ1 Γ2 Δ1 Δ2} →
                      holes-disjoint e1 e2 →
                      Γ1 ⊢ e1 ⇐ τ1 ~> e1' :: τ1' ⊣ Δ1 →
                      Γ2 ⊢ e2 ⇐ τ2 ~> e2' :: τ2' ⊣ Δ2 →
                      Δ1 ## Δ2
  elab-ana-disjoint {e1} {e2} hd ana1 ana2
    with find-holes e1 | find-holes e2
  ... | (_ , he1) | (_ , he2) = dom-eq-disj (holes-disjoint-disjoint he1 he2 hd)
                                            (holes-delta-ana he1 ana1)
                                            (holes-delta-ana he2 ana2)

  elab-synth-disjoint : ∀{e1 e2 τ1 τ2 e1' e2' Γ1 Γ2 Δ1 Δ2} →
                        holes-disjoint e1 e2 →
                        Γ1 ⊢ e1 ⇒ τ1 ~> e1' ⊣ Δ1 →
                        Γ2 ⊢ e2 ⇒ τ2 ~> e2' ⊣ Δ2 →
                        Δ1 ## Δ2
  elab-synth-disjoint {e1} {e2} hd synth1 synth2
    with find-holes e1 | find-holes e2
  ... | (_ , he1) | (_ , he2) = dom-eq-disj (holes-disjoint-disjoint he1 he2 hd)
                                            (holes-delta-synth he1 synth1)
                                            (holes-delta-synth he2 synth2)

  elab-synth-ana-disjoint : ∀{e1 e2 τ1 τ2 e1' e2' τ2' Γ1 Γ2 Δ1 Δ2} →
                            holes-disjoint e1 e2 →
                            Γ1 ⊢ e1 ⇒ τ1 ~> e1' ⊣ Δ1 →
                            Γ2 ⊢ e2 ⇐ τ2 ~> e2' :: τ2' ⊣ Δ2 →
                            Δ1 ## Δ2
  elab-synth-ana-disjoint {e1} {e2} hd synth ana
    with find-holes e1 | find-holes e2
  ... | (_ , he1) | (_ , he2) = dom-eq-disj (holes-disjoint-disjoint he1 he2 hd)
                                            (holes-delta-synth he1 synth)
                                            (holes-delta-ana he2 ana)


------------------------------------------------------------------------
-- A combinator for running two (independent) computations in sequence
------------------------------------------------------------------------

{-# OPTIONS --sized-types #-}

module Delay-monad.Sequential where

open import Equality.Propositional as Eq using (_≡_)
open import Prelude hiding (_+_)
open import Prelude.Size

open import Conat Eq.equality-with-J as Conat
  using (zero; suc; force; _+_)

open import Delay-monad
open import Delay-monad.Bisimilarity
open import Delay-monad.Bisimilarity.Kind
open import Delay-monad.Monad

private

  variable
    a               : Level
    A B C           : Type a
    i               : Size
    k               : Kind
    f₁ f₂           : Delay (A → B) ∞
    x x₁ x₂ y y₁ y₂ : Delay A ∞
    f′              : Delay′ (A → B) ∞
    x′              : Delay′ A ∞
    h               : A → B
    z               : A

------------------------------------------------------------------------
-- The _⊛_ operator

-- Sequential composition of computations.

infixl 6 _⊛_

_⊛_ : Delay (A → B) i → Delay A i → Delay B i
f ⊛ x = f >>=′ λ f → x >>=′ λ x → now (f x)

-- The number of later constructors in f ⊛ x is bisimilar to the sum
-- of the number of later constructors in f and the number of later
-- constructors in x.

steps-⊛∼steps-+-steps :
  (f : Delay (A → B) ∞) →
  Conat.[ i ] steps (f ⊛ x) ∼ steps f + steps x
steps-⊛∼steps-+-steps {x = now _}   (now _)   = zero
steps-⊛∼steps-+-steps {x = later _} (now _)   = suc λ { .force → steps-⊛∼steps-+-steps (now _)    }
steps-⊛∼steps-+-steps               (later f) = suc λ { .force → steps-⊛∼steps-+-steps (f .force) }

-- A rearrangement lemma for _⊛_.

later-⊛∼⊛-later :
  [ i ] later f′ ⊛ x′ .force ∼ f′ .force ⊛ later x′
later-⊛∼⊛-later = lemma _ Eq.refl
  where
  lemma :
    ∀ f → f′ .force ≡ f → [ i ] later f′ ⊛ x′ .force ∼ f ⊛ later x′
  lemma {f′ = f′} {x′ = x′} (now f) eq = later λ { .force →
    f′ .force ⊛ x′ .force  ≡⟨ Eq.cong (_⊛ x′ .force) eq ⟩
    now f ⊛ x′ .force      ∎ }
  lemma {f′ = f′} {x′ = x′} (later f) eq = later λ { .force →
    f′ .force ⊛ x′ .force  ≡⟨ Eq.cong (_⊛ x′ .force) eq ⟩
    later f ⊛ x′ .force    ∼⟨ later-⊛∼⊛-later ⟩∼
    f .force ⊛ later x′    ∎ }

-- The _⊛_ operator preserves strong and weak bisimilarity and
-- expansion.

infixl 6 _⊛-cong_

_⊛-cong_ :
  [ i ] f₁ ⟨ k ⟩ f₂ →
  [ i ] x₁ ⟨ k ⟩ x₂ →
  [ i ] f₁ ⊛ x₁ ⟨ k ⟩ f₂ ⊛ x₂
p ⊛-cong q = p >>=-cong λ _ → q >>=-cong λ _ → now

-- The _⊛_ operator is (kind of) commutative.

⊛-comm :
  (f : Delay (A → B) ∞) (x : Delay A ∞) →
  [ i ] f ⊛ x ∼ map′ (flip _$_) x ⊛ f
⊛-comm (now f)   (now x)   = reflexive _
⊛-comm (now f)   (later x) = later λ { .force → ⊛-comm (now f) (x .force) }
⊛-comm (later f) (now x)   = later λ { .force → ⊛-comm (f .force) (now x) }
⊛-comm (later f) (later x) = later λ { .force →
  f .force ⊛ later x                    ∼⟨ symmetric later-⊛∼⊛-later ⟩
  later f ⊛ x .force                    ∼⟨ ⊛-comm (later f) (x .force) ⟩∼
  map′ (flip _$_) (x .force) ⊛ later f  ∎ }

-- The function map′ can be expressed using _⊛_.

map∼now-⊛ : (x : Delay A ∞) → [ i ] map′ h x ∼ now h ⊛ x
map∼now-⊛ {h = h} x =
  map′ h x                           ∼⟨ map∼>>=-now _ ⟩
  (x >>=′ now ∘ h)                   ∼⟨⟩
  (now h >>=′ λ h → x >>=′ now ∘ h)  ∼⟨⟩
  now h ⊛ x                          ∎

-- The applicative functor laws hold up to strong bisimilarity.

now-id-⊛ : [ i ] now id ⊛ x ∼ x
now-id-⊛ {x = now x}   = now
now-id-⊛ {x = later x} = later λ { .force → now-id-⊛ }

now-∘-⊛-⊛-⊛ :
  (f : Delay (B → C) ∞) (g : Delay (A → B) ∞) (x : Delay A ∞) →
  [ i ] now (λ f → f ∘_) ⊛ f ⊛ g ⊛ x ∼ f ⊛ (g ⊛ x)
now-∘-⊛-⊛-⊛ (now _)   (now _)   (now _)   = now
now-∘-⊛-⊛-⊛ (now _)   (now _)   (later x) = later λ { .force → now-∘-⊛-⊛-⊛ (now _)    (now _)    (x .force) }
now-∘-⊛-⊛-⊛ (now _)   (later g) x         = later λ { .force → now-∘-⊛-⊛-⊛ (now _)    (g .force) x          }
now-∘-⊛-⊛-⊛ (later f) g         x         = later λ { .force → now-∘-⊛-⊛-⊛ (f .force) g          x          }

now-⊛-now : [ i ] now h ⊛ now z ∼ now (h z)
now-⊛-now = now

⊛-now :
  (f : Delay (A → B) ∞) →
  [ i ] f ⊛ now z ∼ now (λ f → f z) ⊛ f
⊛-now (now f)   = now
⊛-now (later f) = later λ { .force → ⊛-now (f .force) }

------------------------------------------------------------------------
-- The _∣_ operator

-- Sequential composition of computations.

infix 10 _∣_

_∣_ : Delay A i → Delay B i → Delay (A × B) i
x ∣ y = map′ _,_ x ⊛ y

-- The number of later constructors in x ∣ y is bisimilar to the sum
-- of the number of later constructors in x and the number of later
-- constructors in y.

steps-∣∼max-steps-steps :
  Conat.[ i ] steps (x ∣ y) ∼ steps x + steps y
steps-∣∼max-steps-steps {x = x} {y = y} =
  steps (x ∣ y)                 Conat.∼⟨ Conat.reflexive-∼ _ ⟩
  steps (map′ _,_ x ⊛ y)        Conat.∼⟨ steps-⊛∼steps-+-steps (map′ _,_ x) ⟩
  steps (map′ _,_ x) + steps y  Conat.∼⟨ steps-map′ x Conat.+-cong Conat.reflexive-∼ _ ⟩∎
  steps x + steps y             ∎∼

-- The _∣_ operator preserves strong and weak bisimilarity and
-- expansion.

infix 10 _∣-cong_

_∣-cong_ :
  [ i ] x₁ ⟨ k ⟩ x₂ →
  [ i ] y₁ ⟨ k ⟩ y₂ →
  [ i ] x₁ ∣ y₁ ⟨ k ⟩ x₂ ∣ y₂
p ∣-cong q = map-cong _,_ p ⊛-cong q

-- The _∣_ operator is commutative (up to swapping of results).

∣-comm : [ i ] x ∣ y ∼ map′ swap (y ∣ x)
∣-comm {x = x} {y = y} =
  x ∣ y                                                          ∼⟨⟩
  map′ _,_ x ⊛ y                                                 ∼⟨ ⊛-comm (map′ _,_ x) y ⟩
  map′ (flip _$_) y ⊛ map′ _,_ x                                 ∼⟨ map∼now-⊛ y ⊛-cong map∼now-⊛ x ⟩
  now (flip _$_) ⊛ y ⊛ (now _,_ ⊛ x)                             ∼⟨ symmetric (now-∘-⊛-⊛-⊛ (now _ ⊛ y) (now _) x) ⟩
  now (λ f → f ∘_) ⊛ (now (flip _$_) ⊛ y) ⊛ now _,_ ⊛ x          ∼⟨ symmetric (now-∘-⊛-⊛-⊛ (now _) (now _) y) ⊛-cong (now _ ∎) ⊛-cong (x ∎) ⟩
  now _∘_ ⊛ now (λ f → f ∘_) ⊛ now (flip _$_) ⊛ y ⊛ now _,_ ⊛ x  ∼⟨⟩
  now (λ x f y → f y x) ⊛ y ⊛ now _,_ ⊛ x                        ∼⟨ ⊛-now (now (λ x f y → f y x) ⊛ y) ⊛-cong (x ∎) ⟩
  now (_$ _,_) ⊛ (now (λ x f y → f y x) ⊛ y) ⊛ x                 ∼⟨ symmetric (now-∘-⊛-⊛-⊛ (now _) (now _) y) ⊛-cong (x ∎) ⟩
  now _∘_ ⊛ now (_$ _,_) ⊛ now (λ x f y → f y x) ⊛ y ⊛ x         ∼⟨⟩
  now (curry swap) ⊛ y ⊛ x                                       ∼⟨⟩
  now _∘_ ⊛ now (swap ∘_) ⊛ now _,_ ⊛ y ⊛ x                      ∼⟨ now-∘-⊛-⊛-⊛ (now _) (now _) y ⊛-cong (x ∎) ⟩
  now (swap ∘_) ⊛ (now _,_ ⊛ y) ⊛ x                              ∼⟨ (now _ ∎) ⊛-cong symmetric (map∼now-⊛ y) ⊛-cong (x ∎) ⟩
  now (swap ∘_) ⊛ map′ _,_ y ⊛ x                                 ∼⟨⟩
  now _∘_ ⊛ now swap ⊛ map′ _,_ y ⊛ x                            ∼⟨ now-∘-⊛-⊛-⊛ (now _) (map′ _,_ y) x ⟩
  now swap ⊛ (map′ _,_ y ⊛ x)                                    ∼⟨ symmetric (map∼now-⊛ _) ⟩
  map′ swap (map′ _,_ y ⊛ x)                                     ∼⟨⟩
  map′ swap (y ∣ x)                                              ∎

-- The _∣_ operator can be expressed using other functions.

∣-∼ : [ i ] x ∣ y ∼ (x >>=′ λ x → y >>=′ λ y → now (x , y))
∣-∼ {x = x} {y = y} =
  (map′ _,_ x >>=′ λ f → y >>=′ λ y → now (f y))                 ∼⟨ (map∼>>=-now x >>=-cong λ _ → _ ∎) ⟩
  ((x >>=′ λ x → now (x ,_)) >>=′ λ f → y >>=′ λ y → now (f y))  ∼⟨ symmetric $ associativity′ x (λ x → now (x ,_)) (λ f → y >>=′ λ y → now (f y)) ⟩
  (x >>=′ λ x → now (x ,_) >>=′ λ f → y >>=′ λ y → now (f y))    ∼⟨⟩
  (x >>=′ λ x → y >>=′ λ y → now (x , y))                        ∎


{-

Constructing List ErrorPart from format strings. Supported formats

  %s : String
  %d : Nat
  %t : Term
  %n : Name
  %e : List ErrorPart

 Examples at the bottom.

-}
module Tactic.Reflection.Printf where

open import Prelude
open import Prelude.Variables
open import Builtin.Reflection

private

  data Format (A : Set) : Set where
    fmtLit  : A → Format A
    fmtStr  : Format A
    fmtNat  : Format A
    fmtTerm : Format A
    fmtName : Format A
    fmtErrs : Format A

  instance
    FunctorFormat : Functor Format
    FunctorFormat .fmap f (fmtLit x) = fmtLit (f x)
    FunctorFormat .fmap f fmtStr = fmtStr
    FunctorFormat .fmap f fmtNat = fmtNat
    FunctorFormat .fmap f fmtTerm = fmtTerm
    FunctorFormat .fmap f fmtName = fmtName
    FunctorFormat .fmap f fmtErrs = fmtErrs

  parseFormat : String → List (Format String)
  parseFormat = map (fmap packString) ∘ parse ∘ unpackString
    where
      cons : Char → List (Format (List Char)) → List (Format (List Char))
      cons c (fmtLit cs ∷ fs) = fmtLit (c ∷ cs) ∷ fs
      cons c fs = fmtLit [ c ] ∷ fs

      parse : List Char → List (Format (List Char))
      parse ('%' ∷ 's' ∷ fmt) = fmtStr  ∷ parse fmt
      parse ('%' ∷ 'd' ∷ fmt) = fmtNat  ∷ parse fmt
      parse ('%' ∷ 't' ∷ fmt) = fmtTerm ∷ parse fmt
      parse ('%' ∷ 'n' ∷ fmt) = fmtName ∷ parse fmt
      parse ('%' ∷ 'e' ∷ fmt) = fmtErrs ∷ parse fmt
      parse ('%' ∷ '%' ∷ fmt) = cons '%' (parse fmt)
      parse (c ∷ fmt)         = cons c (parse fmt)
      parse []                = []

  FormatErrorType : List (Format String) → Set ℓ → Set ℓ
  FormatErrorType [] Res = Res
  FormatErrorType (fmtLit _ ∷ fs) Res = FormatErrorType fs Res
  FormatErrorType (fmtStr   ∷ fs) Res = String         → FormatErrorType fs Res
  FormatErrorType (fmtNat   ∷ fs) Res = Nat            → FormatErrorType fs Res
  FormatErrorType (fmtTerm  ∷ fs) Res = Term           → FormatErrorType fs Res
  FormatErrorType (fmtName  ∷ fs) Res = Name           → FormatErrorType fs Res
  FormatErrorType (fmtErrs  ∷ fs) Res = List ErrorPart → FormatErrorType fs Res

-- Exported for library writers
formatError : (List ErrorPart → A) → (fmt : String) → FormatErrorType (parseFormat fmt) A
formatError {A = A} k = format [] ∘ parseFormat
  where
    format : List ErrorPart → (fmt : List (Format String)) → FormatErrorType fmt A
    format acc [] = k (reverse acc)
    format acc (fmtLit s ∷ fmt)   = format (strErr s        ∷ acc) fmt
    format acc (fmtStr   ∷ fmt) s = format (strErr s        ∷ acc) fmt
    format acc (fmtNat   ∷ fmt) n = format (strErr (show n) ∷ acc) fmt
    format acc (fmtTerm  ∷ fmt) t = format (termErr t       ∷ acc) fmt
    format acc (fmtName  ∷ fmt) x = format (nameErr x       ∷ acc) fmt
    format acc (fmtErrs  ∷ fmt) e = format (reverse e      ++ acc) fmt

-- Public API --

errorPartsFmt : (fmt : String) → FormatErrorType (parseFormat fmt) (List ErrorPart)
errorPartsFmt = formatError id

typeErrorFmt : (fmt : String) → FormatErrorType (parseFormat fmt) (TC A)
typeErrorFmt = formatError typeError

debugPrintFmt : String → Nat → (fmt : String) → FormatErrorType (parseFormat fmt) (TC ⊤)
debugPrintFmt tag lvl = formatError (debugPrint tag lvl)

-- Examples --

private

  _ : Name → Term → List ErrorPart
  _ = errorPartsFmt "%n : %t"

  _ : Name → Nat → Nat → TC A
  _ = typeErrorFmt "%n applied to %d arguments, expected %d"

  _ : Term → String → TC ⊤
  _ = debugPrintFmt "tactic.foo" 10 "solving %t with %s"


module Oscar.Class.Semifunctor where

open import Oscar.Class.Semigroup
open import Oscar.Class.Extensionality
open import Oscar.Class.Preservativity
open import Oscar.Function
open import Oscar.Level
open import Oscar.Relation

record Semifunctor
  {𝔞₁} {𝔄₁ : Set 𝔞₁} {𝔰₁} {_►₁_ : 𝔄₁ → 𝔄₁ → Set 𝔰₁}
    (_◅₁_ : ∀ {m n} → m ►₁ n → ∀ {l} → m ⟨ l ►₁_ ⟩→ n)
  {ℓ₁}
    (_≋₁_ : ∀ {m n} → m ►₁ n → m ►₁ n → Set ℓ₁)
  {𝔞₂} {𝔄₂ : Set 𝔞₂} {𝔰₂} {_►₂_ : 𝔄₂ → 𝔄₂ → Set 𝔰₂}
    (_◅₂_ : ∀ {m n} → m ►₂ n → ∀ {l} → m ⟨ l ►₂_ ⟩→ n)
  {ℓ₂}
    (_≋₂_ : ∀ {m n} → m ►₂ n → m ►₂ n → Set ℓ₂)
  (μ : 𝔄₁ → 𝔄₂)
  (□ : ∀ {m n} → m ►₁ n → μ m ►₂ μ n)
  : Set (𝔞₁ ⊔ 𝔰₁ ⊔ ℓ₁ ⊔ 𝔞₂ ⊔ 𝔰₂ ⊔ ℓ₂) where
  field
    ⦃ ′semigroup₁ ⦄ : Semigroup _◅₁_ _≋₁_
    ⦃ ′semigroup₂ ⦄ : Semigroup _◅₂_ _≋₂_
    ⦃ ′extensionality ⦄ : ∀ {m n} → Extensionality (_≋₁_ {m} {n}) (λ ⋆ → _≋₂_ {μ m} {μ n} ⋆) □ □
    ⦃ ′preservativity ⦄ : ∀ {l m n} → Preservativity (λ ⋆ → _◅₁_ {m = m} {n = n} ⋆ {l = l}) (λ ⋆ → _◅₂_ ⋆) _≋₂_ □ □ □

instance

  Semifunctor⋆ : ∀
    {𝔞₁} {𝔄₁ : Set 𝔞₁} {𝔰₁} {_►₁_ : 𝔄₁ → 𝔄₁ → Set 𝔰₁}
      {_◅₁_ : ∀ {m n} → m ►₁ n → ∀ {l} → m ⟨ l ►₁_ ⟩→ n}
    {ℓ₁}
      {_≋₁_ : ∀ {m n} → m ►₁ n → m ►₁ n → Set ℓ₁}
    {𝔞₂} {𝔄₂ : Set 𝔞₂} {𝔰₂} {_►₂_ : 𝔄₂ → 𝔄₂ → Set 𝔰₂}
      {_◅₂_ : ∀ {m n} → m ►₂ n → ∀ {l} → m ⟨ l ►₂_ ⟩→ n}
    {ℓ₂}
      {_≋₂_ : ∀ {m n} → m ►₂ n → m ►₂ n → Set ℓ₂}
    {μ : 𝔄₁ → 𝔄₂}
    {□ : ∀ {m n} → m ►₁ n → μ m ►₂ μ n}
    ⦃ _ : Semigroup _◅₁_ _≋₁_ ⦄
    ⦃ _ : Semigroup _◅₂_ _≋₂_ ⦄
    ⦃ _ : ∀ {m n} → Extensionality (_≋₁_ {m} {n}) (λ ⋆ → _≋₂_ {μ m} {μ n} ⋆) □ □ ⦄
    ⦃ _ : ∀ {l m n} → Preservativity (λ ⋆ → _◅₁_ {m = m} {n = n} ⋆ {l = l}) (λ ⋆ → _◅₂_ ⋆) _≋₂_ □ □ □ ⦄
    → Semifunctor _◅₁_ _≋₁_ _◅₂_ _≋₂_ μ □
  Semifunctor.′semigroup₁ Semifunctor⋆ = it
  Semifunctor.′semigroup₂ Semifunctor⋆ = it
  Semifunctor.′extensionality Semifunctor⋆ = it
  Semifunctor.′preservativity Semifunctor⋆ = it

-- record Semifunctor'
--   {𝔞} {𝔄 : Set 𝔞} {𝔰} {_►_ : 𝔄 → 𝔄 → Set 𝔰}
--     (_◅_ : ∀ {m n} → m ► n → ∀ {l} → m ⟨ l ►_ ⟩→ n)
--   {ℓ₁}
--     (_≋₁_ : ∀ {m n} → m ► n → m ► n → Set ℓ₁)
--   {𝔱} {▸ : 𝔄 → Set 𝔱}
--     (_◃_ : ∀ {m n} → m ► n → m ⟨ ▸ ⟩→ n)
--   {ℓ₂}
--     (_≋₂_ : ∀ {n} → ▸ n → ▸ n → Set ℓ₂)
--   (□ : ∀ {m n} → m ► n → m ► n)
--   : Set (𝔞 ⊔ 𝔰 ⊔ ℓ₁ ⊔ 𝔱 ⊔ ℓ₂) where
--   field
--     ⦃ ′semigroup₁ ⦄ : Semigroup _◅_ (λ ⋆ → _◅_ ⋆) _≋₁_
--     ⦃ ′semigroup₂ ⦄ : Semigroup _◅_ _◃_ _≋₂_
--     ⦃ ′extensionality ⦄ : ∀ {m} {t : ▸ m} {n} → Extensionality (_≋₁_ {m} {n}) (λ ⋆ → _≋₂_ {n} ⋆) (_◃ t) (_◃ t)
--     ⦃ ′preservativity ⦄ : Preservativity _►₁_ _◃₁_ _►₂_ _◃₂_ _≋₂_ ◽ □

-- record Semifunctor
--   {𝔞₁} {𝔄₁ : Set 𝔞₁} {𝔰₁} {_►₁_ : 𝔄₁ → 𝔄₁ → Set 𝔰₁}
--     (_◅₁_ : ∀ {m n} → m ►₁ n → ∀ {l} → m ⟨ l ►₁_ ⟩→ n)
--   {𝔱₁} {▸₁ : 𝔄₁ → Set 𝔱₁}
--     (_◃₁_ : ∀ {m n} → m ►₁ n → m ⟨ ▸₁ ⟩→ n)
--   {ℓ₁}
--     (_≋₁_ : ∀ {n} → ▸₁ n → ▸₁ n → Set ℓ₁)
--   {𝔞₂} {𝔄₂ : Set 𝔞₂} {𝔰₂} {_►₂_ : 𝔄₂ → 𝔄₂ → Set 𝔰₂}
--     (_◅₂_ : ∀ {m n} → m ►₂ n → ∀ {l} → m ⟨ l ►₂_ ⟩→ n)
--   {𝔱₂} {▸₂ : 𝔄₂ → Set 𝔱₂}
--     (_◃₂_ : ∀ {m n} → m ►₂ n → m ⟨ ▸₂ ⟩→ n)
--   {ℓ₂}
--     (_≋₂_ : ∀ {n} → ▸₂ n → ▸₂ n → Set ℓ₂)
--   (μ : 𝔄₁ → 𝔄₂)
--   (◽ : ∀ {n} → ▸₁ n → ▸₂ (μ n))
--   (□ : ∀ {m n} → m ►₁ n → μ m ►₂ μ n)
--   : Set (𝔞₁ ⊔ 𝔰₁ ⊔ 𝔱₁ ⊔ ℓ₁ ⊔ 𝔞₂ ⊔ 𝔰₂ ⊔ 𝔱₂ ⊔ ℓ₂) where
--   field
--     ⦃ ′semigroup₁ ⦄ : Semigroup _◅₁_ _◃₁_ _≋₁_
--     ⦃ ′semigroup₂ ⦄ : Semigroup _◅₂_ _◃₂_ _≋₂_
--     ⦃ ′extensionality ⦄ : ∀ {n} → Extensionality (_≋₁_ {n}) (λ ⋆ → _≋₂_ {μ n} ⋆) ◽ ◽
--     ⦃ ′preservativity ⦄ : Preservativity _►₁_ _◃₁_ _►₂_ _◃₂_ _≋₂_ ◽ □

-- open Semifunctor ⦃ … ⦄ public hiding (′semigroup₁; ′semigroup₂; ′extensionality; ′preservativity)

-- instance

--   Semifunctor⋆ : ∀
--     {𝔞₁} {𝔄₁ : Set 𝔞₁} {𝔰₁} {_►₁_ : 𝔄₁ → 𝔄₁ → Set 𝔰₁}
--       {_◅₁_ : ∀ {m n} → m ►₁ n → ∀ {l} → m ⟨ l ►₁_ ⟩→ n}
--     {𝔱₁} {▸₁ : 𝔄₁ → Set 𝔱₁}
--       {_◃₁_ : ∀ {m n} → m ►₁ n → m ⟨ ▸₁ ⟩→ n}
--     {ℓ₁}
--       {_≋₁_ : ∀ {n} → ▸₁ n → ▸₁ n → Set ℓ₁}
--     {𝔞₂} {𝔄₂ : Set 𝔞₂} {𝔰₂} {_►₂_ : 𝔄₂ → 𝔄₂ → Set 𝔰₂}
--       {_◅₂_ : ∀ {m n} → m ►₂ n → ∀ {l} → m ⟨ l ►₂_ ⟩→ n}
--     {𝔱₂} {▸₂ : 𝔄₂ → Set 𝔱₂}
--       {_◃₂_ : ∀ {m n} → m ►₂ n → m ⟨ ▸₂ ⟩→ n}
--     {ℓ₂}
--       {_≋₂_ : ∀ {n} → ▸₂ n → ▸₂ n → Set ℓ₂}
--     {μ : 𝔄₁ → 𝔄₂}
--     {◽ : ∀ {n} → ▸₁ n → ▸₂ (μ n)}
--     {□ : ∀ {m n} → m ►₁ n → μ m ►₂ μ n}
--     ⦃ _ : Semigroup _◅₁_ _◃₁_ _≋₁_ ⦄
--     ⦃ _ : Semigroup _◅₂_ _◃₂_ _≋₂_ ⦄
--     ⦃ _ : ∀ {n} → Extensionality (_≋₁_ {n}) (λ ⋆ → _≋₂_ {μ n} ⋆) ◽ ◽ ⦄
--     ⦃ _ : Preservativity _►₁_ _◃₁_ _►₂_ _◃₂_ _≋₂_ ◽ □ ⦄
--     → Semifunctor _◅₁_ _◃₁_ _≋₁_ _◅₂_ _◃₂_ _≋₂_ μ ◽ □
--   Semifunctor.′semigroup₁ Semifunctor⋆ = it
--   Semifunctor.′semigroup₂ Semifunctor⋆ = it
--   Semifunctor.′extensionality Semifunctor⋆ = it
--   Semifunctor.′preservativity Semifunctor⋆ = it