\begin{code}
{-# OPTIONS --safe #-}

open import Prelude
open import FreeMonad.PackagedTheory
open import Signatures
open import FreeMonad.Theory
open import FreeMonad.FindVars
open import Finite

module FreeMonad.HElim
  (T : FullTheory ℓzero)
  (findVar : FindVars (FullTheory.𝒯 T))
  (fin-arities :  Oᵢ   (Signature.Arity (FullTheory.𝔽 T) Oᵢ))
  where

open FullTheory T

open import FreeMonad.Syntax 𝔽
open SyntaxBind using (_<$>_; <$>-comp) renaming (_>>=_ to _>>=ₛ_)
open import FreeMonad.Syntax.AsSignature 𝔽 hiding (_∈_ ; _∉_)
open import FreeMonad.Relation 𝔽 𝒯
open import Truth
open import Data.Sigma.Properties
open import Data.Lift.Properties

open Theory 𝒯

open Signature 𝔽 using (Arity)

disc-arities :  Oᵢ  Discrete (Arity Oᵢ)
disc-arities Oᵢ = ℰ⇒Discrete (fin-arities Oᵢ)

setArity :  Oᵢ  isSet (Arity Oᵢ)
setArity O = Discrete→isSet (disc-arities O)


infixr 5 _∈_ _∉_
_∈_ _∉_ : A  (B  A)  Type _
_∈_ = flip Fibre
x  xs = ¬ (x  xs)

ℰ⟨Index⟩ : (s : Syntax A)   (Index s)
ℰ⟨Index⟩ = elim-s _ λ { (var x)  ℰ⟨⊤⟩ ; (op (O , k) P⟨xs⟩)  ℰΣ (fin-arities O) P⟨xs⟩ }

∈? : Discrete A  (x : A) (s : Syntax B) (xs : Index s  A)  Dec  x  xs 
∈? discrete x s xs = ℰ⇒search (ℰ⟨Index⟩ s) λ i  discrete (xs i) x


module _ {A : Type a} where
  record Provenance (s t : Syntax A) : Type (ℓsuc a)

\end{code}

%<*principal-derivation-head>
\begin{code}
  record Provenance s t where
    field
\end{code}
%</principal-derivation-head>
%<*principal-derivation-ivp>
\begin{code}
      Iₚ    : Type a
      I≟    : Discrete Iₚ
\end{code}
%</principal-derivation-ivp>
%<*principal-derivation-stp>
\begin{code}
      sₚ  : Index s  Iₚ
      tₚ  : Index t  Iₚ
\end{code}
%</principal-derivation-stp>
%<*principal-derivation-eqv>
\begin{code}
      eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
\end{code}
%</principal-derivation-eqv>
%<*principal-derivation-k>
\begin{code}
      kₚ   : Iₚ  A
      s-k  :  i  lookup s i  kₚ (sₚ i)
      t-k  :  i  lookup t i  kₚ (tₚ i)
\end{code}
%</principal-derivation-k>
\begin{code}

    ∈s? :  v  Dec  v  sₚ 
    ∈s? v = ∈? I≟ v s sₚ

    ∈t? :  v  Dec  v  tₚ 
    ∈t? v = ∈? I≟ v t tₚ


module DeriveEq {A : Type a} (i : Laws) (Γ : Eqns i .Γ) (k : Eqns i .ν Γ  Syntax A) where
  open Equation (eqn (Eqns i) Γ) renaming (lhs to lhs∙ ; rhs to rhs∙) using ()


  private module DisplayI where

    Iₚ : Type
\end{code}
%<*eq-i>
\begin{code}
    Iₚ = Σ[ v  Eqns i .ν Γ ] × Index (k v)
\end{code}
%</eq-i>
\begin{code}

  Iₚ : Type a
  Iₚ = Lift (Σ[ v  Eqns i .ν Γ ] × Index (k v))

  I≟ : Discrete Iₚ
  I≟ = discreteLift (discreteΣ (findVar i Γ) λ x  disc-index disc-arities (k x))

  kₚ : Iₚ  A
  kₚ = uncurry (lookup ∘′ k)  lower

  sₚ : >>=-ind lhs∙ k  Iₚ
  sₚ = lift  map-Σ (lookup lhs∙) id ∘′ pull-shape k lhs∙

  tₚ : >>=-ind rhs∙ k  Iₚ
  tₚ = lift  map-Σ (lookup rhs∙) id ∘′ pull-shape k rhs∙ 

  eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
  eqvₚ = reflₜ (>>=⟦⟧ k _ lhs∙)
     transₜ  eqₜ i Γ  v  fill _ (lift  (v ,_)))
     transₜ  reflₜ (sym (>>=⟦⟧ k _ rhs∙))

  s-k :  i  lookup (lhs∙ >>=ₛ k) i  kₚ (sₚ i)
  s-k i = sym (lookup-pull k lhs∙ i)

  t-k :  i  lookup (rhs∙ >>=ₛ k) i  kₚ (tₚ i)
  t-k i = sym (lookup-pull k rhs∙ i)


module DeriveCong {A : Type a} Oᵢ (kₗ kᵣ : Arity Oᵢ  Syntax A) (derive-at :  i  Provenance (kₗ i) (kᵣ i)) where
  module _ (i : Arity Oᵢ) where module 𝒟 = Provenance (derive-at i)

  s t : Syntax A
  s = op (Oᵢ , kₗ)
  t = op (Oᵢ , kᵣ)

  Iₚ : Type _
\end{code}
%<*cong-i>
\begin{code}
  Iₚ = Σ[ i  Arity Oᵢ ] × 𝒟.Iₚ i
\end{code}
%</cong-i>
\begin{code}

  I≟ : Discrete Iₚ
  I≟ = discreteΣ (disc-arities _) 𝒟.I≟

  sₚ : Index s  Iₚ
  sₚ (i , is) = i , 𝒟.sₚ i is

  tₚ : Index t  Iₚ
  tₚ (i , is) = i , 𝒟.tₚ i is

  eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
  eqvₚ = congₜ Oᵢ _ _ λ i 
              reflₜ (sym (<$>-com-inv (_,_ i) (_ , 𝒟.sₚ i)))
     transₜ  <$>-cong (_,_ i) (𝒟.eqvₚ i)
     transₜ  reflₜ (<$>-com-inv (_,_ i) (_ , 𝒟.tₚ i))

  kₚ : Iₚ  A
  kₚ = uncurry 𝒟.kₚ

  s-k :  i  lookup s i  kₚ (sₚ i)
  s-k = uncurry 𝒟.s-k

  t-k :  i  lookup t i  kₚ (tₚ i)
  t-k = uncurry 𝒟.t-k


module DeriveTrans {A : Type a} (setA : isSet A) (s t u : Syntax A) (lhs′ : Provenance s u) (rhs′ : Provenance u t) where
  module rhs = Provenance rhs′
  module lhs = Provenance lhs′

  open import HITs.Pushout
  open import HITs.Pushout.Properties using (fin-disc-pushout)

  Iₚ : Type a
\end{code}
%<*trans-i>
\begin{code}[number=eq:trans-i]
  Iₚ = Pushout {A = Index u} {B = lhs.Iₚ} {C = rhs.Iₚ} lhs.tₚ rhs.sₚ
\end{code}
%</trans-i>
\begin{code}

  I≟ : Discrete Iₚ
  I≟ = fin-disc-pushout (ℰ⟨Index⟩ u) lhs.I≟ rhs.I≟

\end{code}
%<*trans-k-1>
\begin{code}
  kₚ : Iₚ  A
  kₚ (inl x)  = lhs.kₚ x
  kₚ (inr x)  = rhs.kₚ x
\end{code}
%</trans-k-1>
%<*trans-k-2>
\begin{code}
  kₚ (push i j) = (sym (lhs.t-k i) ; rhs.s-k i) j
  kₚ (trunc x y p q i j) =
    setA (kₚ x) (kₚ y) (cong kₚ p) (cong kₚ q) i j
\end{code}
%</trans-k-2>

\begin{code}

\end{code}
%<*trans-sp>
\begin{code}
  sₚ : Index s  Iₚ
  sₚ = inl  lhs.sₚ
\end{code}
%</trans-sp>
%<*trans-tp>
\begin{code}
  tₚ : Index t  Iₚ
  tₚ = inr  rhs.tₚ
\end{code}
%</trans-tp>
\begin{code}

  eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
  eqvₚ =       subst₂ _~ₜ_ (<$>-com-inv inl (_ , lhs.sₚ)) (<$>-com-inv inl (_ , lhs.tₚ)) (<$>-cong inl lhs.eqvₚ)
     transₜ  reflₜ (cong (fill (shape u)) (funExt push))
     transₜ  subst₂ _~ₜ_ (<$>-com-inv inr (_ , rhs.sₚ)) (<$>-com-inv inr (_ , rhs.tₚ)) (<$>-cong inr rhs.eqvₚ)

  s-k = lhs.s-k
  t-k = rhs.t-k


private module DisplayDeriveRefl {A : Type} (s : Syntax A) where
  Iₚ : Type
\end{code}
%<*refl-i>
\begin{code}[inline]
  Iₚ = Index s
\end{code}
%</refl-i>
\begin{code}

  I≟ : Discrete Iₚ
  I≟ = disc-index disc-arities s

  sₚ tₚ : Index s  Iₚ
  sₚ = id
  tₚ = id

  eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
  eqvₚ = reflₜ refl

  kₚ : Iₚ  A
\end{code}
%<*k-i>
\begin{code}[inline]
  kₚ = lookup s
\end{code}
%</k-i>
\begin{code}

  s-k t-k :  i  lookup s i  kₚ i
  s-k _ = refl
  t-k _ = refl

module DeriveRefl {A : Type a} (s : Syntax A) where
  Iₚ : Type a
  Iₚ = Lift (Index s)

  I≟ : Discrete Iₚ
  I≟ = discreteLift (disc-index disc-arities s)

  sₚ tₚ : Index s  Iₚ
  sₚ = lift
  tₚ = lift

  eqvₚ : fill _ sₚ ~ₜ fill _ tₚ
  eqvₚ = reflₜ refl

  kₚ : Iₚ  A
  kₚ = lookup s  lower

  s-k t-k :  i  lookup s i  kₚ (lift i)
  s-k _ = refl
  t-k _ = refl


module DeriveSym {A : Type a} (s t : Syntax A) (derived-rev : Provenance t s) where
  module 𝓈 = Provenance derived-rev

  Iₚ = 𝓈.Iₚ
  I≟ = 𝓈.I≟
  sₚ = 𝓈.tₚ
  tₚ = 𝓈.sₚ
  eqvₚ = symₜ 𝓈.eqvₚ
  kₚ  = 𝓈.kₚ
  s-k = 𝓈.t-k
  t-k = 𝓈.s-k


module _ (setA : isSet A) where
  open Theory 𝒯
  open import FinitenessConditions using (SplitQuotientedChoice⇒Choice)

  derive : (s t : Syntax A)  s ~ₜ t   Provenance s t 
  derive _ _ (eqₜ i Γ k)        =  record { DeriveEq i Γ k } 
  derive s t (reflₜ p)          =  subst (Provenance s) p (record { DeriveRefl s }) 
  derive s t (symₜ p)           = ∥map∥  p′  record { DeriveSym s t p′ }) (derive _ _ p)
  derive s t (transₜ p q)       = ∥liftA2∥  lhs rhs  record { DeriveTrans setA s t _ lhs rhs }) (derive _ _ p) (derive _ _ q)
  derive s t (truncₜ p q i)     = squash (derive s t p) (derive s t q) i
  derive _ _ (congₜ Oᵢ kₗ kᵣ x) =
    ∥map∥  d  record { DeriveCong Oᵢ kₗ kᵣ d })
          (SplitQuotientedChoice⇒Choice (setArity Oᵢ) (finiteArity Oᵢ) λ i  derive (kₗ i) (kᵣ i) (x i))

private
  variable
    s : Syntax A
    t : Syntax B


import FreeMonad.Quotiented T as OnTerm

open OnTerm
  using (syntactic-bind; join; >>=-join-eq; >>=-join-comm; <$>-comm)
  renaming (_<$>_ to _<$>ₜ_ ; _>>=_ to _>>=ₜ_ ; return to returnₜ)

module _ {A : Type a} (ϕ : A  Ω c)
  where
  ϕ? : A  A × Ω c
  ϕ? x = x , ϕ x

  ϕT : A  A × Ω c
  ϕT x = x , True

  module _ (setA : isSet A) where

    -- This can be cleaned up a lot: since we switched to the container rep, a lot
    -- of the proofs here should trivialise if stated carefully.
    module _ (setB : isSet B)
            (f g : A  B) (k : (x : A)  ProofOf (ϕ x)  f x  g x)
            (s : Syntax A)          
            (pr : ϕ? <$> s ~ₜ ϕT <$> s)
            where
      𝒢-elim′ : f <$> s ~ₜ g <$> s

      𝒢-elim′ = ∥rec∥ truncₜ Helper.theorem (derive (isSetΣ setA  _  isSetΩ)) (ϕ? <$> s) (ϕT <$> s) pr)
        where
        module Helper
          (d : Provenance (ϕ? <$> s) (ϕT <$> s)) where
          open Provenance d

          k′ : Iₚ  B
\end{code}
%<*kp>
\begin{code}
          k′ i =  let v = kₚ i .fst in if  does (∈s? i) then f v else g v
\end{code}
%</kp>
%<*spf>
\begin{code}
          ∈sₚ⇒f : (i : Index (ϕ? <$> s))  k′ (sₚ i)  f (fst (kₚ (sₚ i)))
\end{code}
%</spf>
\begin{code}
          ∈sₚ⇒f i with ∈s? (sₚ i)
          ... | yes _ = refl
          ... | no ¬p = absurd (¬p  i , refl )

\end{code}
%<*ts1>
\begin{code}
          ts₁ : f <$> s  k′ <$> fill _ sₚ
\end{code}
%</ts1>
\begin{code}
          ts₁ =
            f <$> s                                ≡⟨ <$>-comp s _ _ 
            f  fst <$> ϕ? <$> s                   ≡˘⟨ cong (f  fst <$>_) (Indexed .leftInv (ϕ? <$> s)) 
            f  fst <$> fill _ (lookup (ϕ? <$> s)) ≡⟨ cong ((f  fst <$>_)  fill _) (funExt s-k) 
            f  fst <$> fill _ (kₚ  sₚ)           ≡⟨ <$>-com-inv (f  fst) _ 
            fill _ (f  fst  kₚ  sₚ)             ≡˘⟨ cong (fill _) (funExt ∈sₚ⇒f) 
            fill _ (k′  sₚ)                       ≡˘⟨ <$>-com-inv k′ _ 
            k′ <$> fill _ sₚ 
\end{code}
%<*tpg>
\begin{code}
          ∈tₚ⇒g : (i : Index (ϕT <$> s))  k′ (tₚ i)  g (fst (kₚ (tₚ i)))
\end{code}
%</tpg>
\begin{code}
          ∈tₚ⇒g i with ∈s? (tₚ i)
          ... | no _ = refl
          ... | yes j,q = flip (∥rec∥ (setB _ _)) j,q  { (j , q) 
              k (kₚ (tₚ i) .fst) $ extract $
                ϕ (kₚ (tₚ i) .fst)            ≡˘⟨ cong (ϕ  fst) (s-k j ; cong kₚ q) 
                ϕ (lookup (ϕ? <$> s)  j .fst) ≡⟨ cong (ϕ  fst) (<$>-lookup ϕ? s j) ; cong snd (sym (<$>-lookup ϕ? s j))  
                lookup (ϕ? <$> s) j .snd      ≡⟨ cong snd (s-k j ; cong kₚ q ; sym (t-k i)) 
                lookup (ϕT <$> s) i .snd      ≡⟨ cong snd (<$>-lookup ϕT s i) 
                True 
              })

\end{code}
%<*ts2>
\begin{code}
          ts₂ : g <$> s  k′ <$> fill _ tₚ
\end{code}
%</ts2>
\begin{code}
          ts₂ = 
            g <$> s                                ≡⟨ <$>-comp s _ _ 
            g  fst <$> ϕT <$> s                   ≡˘⟨ cong (g  fst <$>_) (Indexed .leftInv (ϕT <$> s)) 
            g  fst <$> fill _ (lookup (ϕT <$> s)) ≡⟨ cong ((g  fst <$>_)  fill _) (funExt t-k) 
            g  fst <$> fill _ (kₚ  tₚ)           ≡⟨ <$>-com-inv (g  fst) _ 
            fill _ (g  fst  kₚ  tₚ)             ≡˘⟨ cong (fill _) (funExt ∈tₚ⇒g)  
            fill _ (k′  tₚ)                       ≡˘⟨ <$>-com-inv k′ _ 
            k′ <$> fill _ tₚ 

          theorem : f <$> s ~ₜ g <$> s
          theorem = reflₜ ts₁  transₜ  <$>-cong k′ eqvₚ  transₜ  reflₜ (sym ts₂)

    module _ {B : Type b}
            (f g : A  Term B) (k : (x : A)  ProofOf (ϕ x)  f x  g x) (p : Term A)
            (path : ϕ? <$>ₜ p  ϕT <$>ₜ p)
            where
      𝒢-elim″ : p >>=ₜ f  p >>=ₜ g
      𝒢-elim″ = elimProp/-with {D = λ p  (ϕ? <$>ₜ p)  (ϕT <$>ₜ p)}  p _  squash/ (p >>=ₜ f) (p >>=ₜ g)) go p path
        where
        module _ (pₛ : Syntax A) (path : ϕ? <$>ₜ [ pₛ ]  ϕT <$>ₜ [ pₛ ]) where
          d : ϕ? <$> pₛ ~ₜ ϕT <$> pₛ
          d = ~ₜ-effective (ϕ? <$>  pₛ) (ϕT <$> pₛ) (sym (<$>-comm pₛ ϕ?) ; path ; <$>-comm pₛ ϕT)

          h : ([ f <$> pₛ ]  Term _)  [ g <$> pₛ ]
          h = eq/ _ _ (𝒢-elim′ squash/ f g k pₛ d)

          go : [ pₛ ] >>=ₜ f  [ pₛ ] >>=ₜ g
          go =
            [ pₛ ] >>=ₜ f        ≡⟨ >>=-join-comm pₛ f 
            [ f <$> pₛ ] >>=ₜ id ≡⟨ cong (_>>=ₜ id) h 
            [ g <$> pₛ ] >>=ₜ id ≡˘⟨ >>=-join-comm pₛ g 
            [ pₛ ] >>=ₜ g 

𝒢-elim  :  (ϕ : A  Ω c)
          (f g : A  Term B)
          (k : (x : A)  ProofOf (ϕ x)  f x  g x)
          (p : Term A)
          (ϕ? ϕ <$>ₜ p  ϕT ϕ <$>ₜ p)
          (p >>=ₜ f)  (p >>=ₜ g)
𝒢-elim ϕ f g k p path =
  let h = 𝒢-elim″
             (∥rec∥₂ isSetΩ ϕ)
             squash₂
             (∥rec∥₂ squash/ f)
             (∥rec∥₂ squash/ g)
             (∥elim∥₂  _  isSet→ (isProp→isSet (squash/ _ _))) k)
             (∣_∣₂ <$>ₜ p)
             ( OnTerm.<$>-comp _ _ p
             ; sym (OnTerm.<$>-comp (map₁ ∣_∣₂) (ϕ? ϕ) p)
             ; cong (map₁ ∣_∣₂ <$>ₜ_) path
             ; OnTerm.<$>-comp (map₁ ∣_∣₂) (ϕT ϕ) p
             ; sym (OnTerm.<$>-comp _ _ p))
  in sym (OnTerm.assoc p (OnTerm.return  ∣_∣₂) (∥rec∥₂ squash/ f)) ; h ; OnTerm.assoc p (OnTerm.return  ∣_∣₂) (∥rec∥₂ squash/ g)
\end{code}