{-# OPTIONS --safe #-}

module Multiset.ListQuotient.ListFinality where

open import Multiset.Prelude
open import Multiset.Util.Path using (substIso)
open import Multiset.Functor using (NatTrans ; NatEquiv)
open import Multiset.Util.Vec as VecExt using ()
open import Multiset.Util.BundledVec as BVec
  using
    ( ΣVec
    ; #_
    ; ΣVecPathP
    ; ΣVecIsoΣ
    ; ΣVecUnit*-ℕ-Iso
    )
open import Multiset.Limit.Chain as Chain
  using
    ( lim
    ; Limit
    ; Chain
    ; isSet→LimitPathExt
    ; isLimit
    ; isOfHLevelLimit
    )
open import Multiset.Coalgebra
  using
    ( CoalgebraMorphism
    ; isCoalgebraMorphism
    ; isTerminal
    ; isSet→isPropIsCoalgebraMorphism
    )
open import Multiset.Limit.TerminalChain as TerminalChain
  using
    ( Functor
    ; Lim
    ; ShLim
    ; _^_
    ; _map-!^_
    ; isShLim
    ; isLim
    ; isLimitPreserving
    ; ShLim→Lim
    )

open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Function using (_∘_ ; ∘-assoc ; flip)
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Transport using (substEquiv)
open import Cubical.Foundations.Univalence using (ua)
open import Cubical.Functions.FunExtEquiv using (funExtDep)
open import Cubical.Data.Sigma as Sigma
  using
    ( ΣPathP
    ; Σ≡Prop
    ; Σ-cong-iso-fst
    ; Σ-cong-iso-snd
    )
open import Cubical.Data.Nat.Base as Nat using ( ; suc ; zero ; _+_)
open import Cubical.Data.FinData as Fin using (FinVec)
open import Cubical.Data.Unit.Base using (Unit* ; tt*)
open import Cubical.Data.Unit.Properties as Unit using (isOfHLevelUnit*)
open import Cubical.Data.Vec.Base as Vec using (Vec)
open import Cubical.Data.Vec.Properties as Vec using (FinVecIsoVec)

open ΣVec

instance
  FunctorVec :  {n}  Functor {ℓ-zero}  A  Vec A n)
  FunctorVec .Functor.map = Vec.map
  FunctorVec .Functor.map-id = VecExt.vec-map-id
  FunctorVec .Functor.map-comp = λ g f as  VecExt.vec-map-comp g f as

  FunctorΣVec : Functor {ℓ-zero} ΣVec
  FunctorΣVec .Functor.map = BVec.map
  FunctorΣVec .Functor.map-id = BVec.map-id
  FunctorΣVec .Functor.map-comp = BVec.map-comp

module _ where
  open import Cubical.Data.List as List using (List)
  private
    instance
      FunctorList : Functor {ℓ-zero} List
      FunctorList .Functor.map = List.map
      FunctorList .Functor.map-id {X = X} = go where
        go : (xs : List X)  _
        go List.[] = refl
        go (x List.∷ xs) = cong (x List.∷_) (go xs)
      FunctorList .Functor.map-comp {X} {Y} {Z} g f = go where
        go : (xs : List X)  _
        go List.[] = refl
        go (x List.∷ xs) = cong (g (f x) List.∷_) (go xs)

  ΣVec→List : {A : Type}  ΣVec A  List A
  ΣVec→List (# Vec.[]) = List.[]
  ΣVec→List (# (a Vec.∷ as)) = a List.∷ ΣVec→List (# as)

  List→ΣVec : {A : Type}  List A  ΣVec A
  List→ΣVec List.[] = BVec.[]
  List→ΣVec (a List.∷ as) = a BVec.#∷ List→ΣVec as

  ΣVec⇒List : NatTrans { = ℓ-zero} ΣVec List
  ΣVec⇒List .NatTrans.mor = ΣVec→List
  ΣVec⇒List .NatTrans.is-nat {X = X} f = funExt is-nat where
    is-nat : (xs : ΣVec X)  ΣVec→List (BVec.map f xs)  List.map f (ΣVec→List xs)
    is-nat (# Vec.[]) = refl {x = List.[]}
    is-nat (# (x Vec.∷ xs)) = cong (f x List.∷_) (is-nat (# xs))

  ΣVec-List-EquivNat : NatEquiv ΣVec List
  ΣVec-List-EquivNat .fst = ΣVec⇒List
  ΣVec-List-EquivNat .snd {X} = isoToIsEquiv nat-iso where
    nat-iso : Iso (ΣVec X) (List X)
    nat-iso .Iso.fun = ΣVec→List
    nat-iso .Iso.inv = List→ΣVec
    nat-iso .Iso.rightInv = right-inv where
      right-inv : (xs : List _)  _  xs
      right-inv List.[] = refl
      right-inv (x List.∷ xs) = cong (x List.∷_) (right-inv xs)
    nat-iso .Iso.leftInv = left-inv where
      left-inv : (xs : ΣVec _)  _  xs
      left-inv (# Vec.[]) = refl
      left-inv (# (x Vec.∷ xs)) = cong (x BVec.#∷_) (left-inv (# xs))

open Functor ⦃...⦄

isSetΣVec^ :  n  isSet (ΣVec ^ n)
isSetΣVec^ zero = isOfHLevelUnit* 2
isSetΣVec^ (suc n) = BVec.isSetΣVec (isSetΣVec^ n)

!^ : (n : )  ΣVec ^ (suc n)  ΣVec ^ n
!^ = ΣVec map-!^_

Tree : Type
Tree = Lim ΣVec

isTree : ((n : )  ΣVec ^ n)  Type
isTree = isLim ΣVec

pres⁺ : ΣVec (Lim ΣVec)  ShLim ΣVec
pres⁺ = TerminalChain.pres ΣVec

cut : (n : )  Lim ΣVec  ΣVec ^ n
cut = TerminalChain.cut ΣVec

width : ShLim ΣVec  (d : )  
width tree d = length $ tree .Limit.elements d

vecs : (tree : ShLim ΣVec)  (d : )  Vec (ΣVec ^ d) (width tree d)
vecs tree d = tree .Limit.elements d .vec

widthConstSuc :  (tree : ShLim ΣVec) n  width tree n  width tree (suc n)
widthConstSuc (lim tree is-lim) n = cong length (sym $ is-lim n)

open Iso
open Limit

module ConstLength (len₀ : ) (xs : (d : )  Vec (ΣVec ^ d) len₀) where
  
  constLengthApprox : (d : )  ΣVec ^ (suc d)
  constLengthApprox d .length = len₀
  constLengthApprox d .vec = xs d

  module _ (islim : isShLim ΣVec constLengthApprox) where

    constLengthLim : ShLim ΣVec
    constLengthLim .elements = constLengthApprox
    constLengthLim .is-lim = islim

    inh' : FinVec (Lim ΣVec) len₀
    inh' k .elements = Vec.lookup k  xs
    inh' k .is-lim d =
      (!^ d) (Vec.lookup k $ xs (suc d))           ≡⟨ sym (VecExt.lookup-map (!^ d) (xs (suc d)) k) 
      (Vec.lookup k $ Vec.map (!^ d) (xs (suc d))) ≡⟨ cong (Vec.lookup k) (BVec.mk-vec-inj (islim d)) ⟩∎
      (Vec.lookup k $ xs d) 

    inh : ΣVec (Lim ΣVec)
    inh .length = len₀
    inh .vec = Vec.FinVec→Vec inh'

    abstract
      inh∈fiber : pres⁺ inh  constLengthLim
      inh∈fiber = TerminalChain.isSet→ShLimPath ΣVec (isSetΣVec^  suc) ptwise where
        ptwise :  n   pres⁺ inh .elements n  constLengthApprox n
        ptwise n = cong #_ $
          Vec.map (cut n) (Vec.FinVec→Vec inh') ≡⟨ VecExt.lookup⁻¹-map (cut n) inh' 
          Vec.FinVec→Vec (cut n  inh')         ≡⟨ Vec.Vec→FinVec→Vec (xs n) ⟩∎
          xs n 

    inhFibers : fiber pres⁺ constLengthLim
    inhFibers = inh , inh∈fiber

inhFibers : (base : ShLim ΣVec)  fiber pres⁺ base
inhFibers base = subst (fiber pres⁺) shlim≡ (ConstLength.inhFibers length₀ approx approx-is-shlim) where
  length₀ : 
  length₀ = base .elements 0 .length

  constLength :  d  base .elements d .length  length₀
  constLength zero = refl
  constLength (suc d) = cong length (base .is-lim d)  constLength d

  approx : (d : )  Vec (ΣVec ^ d) length₀
  approx d = subst (Vec (ΣVec ^ d)) (constLength d) (base .elements d .vec)

  approx≡ :  d  base .elements d  # (approx d)
  approx≡ d = ΣVecPathP (constLength d) $ subst-filler (Vec (ΣVec ^ d)) (constLength d) (base .elements d .vec)

  approx-is-shlim : isShLim ΣVec (ConstLength.constLengthApprox length₀ approx)
  approx-is-shlim = subst (isShLim ΣVec) (funExt approx≡) (base .is-lim)

  shlim≡ : ConstLength.constLengthLim length₀ approx approx-is-shlim  base
  shlim≡ = TerminalChain.isSet→ShLimPath ΣVec (isSetΣVec^  suc) (sym  approx≡)

module Direct where
  pres⁻ : ShLim ΣVec  ΣVec (Lim ΣVec)
  pres⁻ = fst  inhFibers

  pres-section : section pres⁺ pres⁻
  pres-section = snd  inhFibers

private
  open import Multiset.Util.Trace as Trace
    using (Trace ; constTrace ; TraceIso)

  OverTrace : Trace   Type
  OverTrace as =
    Σ[ vs  ((n : )  Vec (ΣVec ^ n) (as .Trace.step n)) ]
     (n : ) 
      PathP  i  Vec (ΣVec ^ n) (as .Trace.connect n i))
        (vs n)
        (Vec.map (!^ n) (vs (suc n)))

pres-Iso : Iso (ShLim ΣVec) (ΣVec (Lim ΣVec))
pres-Iso =
  let snd-iso :  n  Iso (OverTrace (constTrace n)) (Vec (Lim ΣVec) n)
      snd-iso n =
        OverTrace (constTrace n)  Iso⟨ toVecLimit n 
        Limit (vecChain n)        Iso⟨ toFinVecOfLimits n 
        FinVec (Lim ΣVec) n   Iso⟨ Vec.FinVecIsoVec n 
        Vec (Lim ΣVec) n          ∎Iso
  in
  ShLim ΣVec                    Iso⟨ toTraceFirstIso 
  Σ (Trace ) OverTrace         Iso⟨ invIso (Σ-cong-iso-fst (invIso TraceIso)) 
  Σ  (OverTrace  constTrace)  Iso⟨ Σ-cong-iso-snd snd-iso 
  Σ  (Vec (Lim ΣVec))          Iso⟨ invIso ΣVecIsoΣ 
  ΣVec (Lim ΣVec) ∎Iso
  where

  TraceFirst : Type
  TraceFirst = Σ (Trace ) OverTrace

  toTraceFirstIso : Iso (ShLim ΣVec) TraceFirst
  toTraceFirstIso .fun tree = trace , vecs tree , vecs-coh where
    trace : Trace 
    trace = width tree , widthConstSuc tree

    vecs-coh :  n
       PathP  i  Vec (ΣVec ^ n) (widthConstSuc tree n i))
        (vecs tree n)
        (Vec.map (!^ n) (vecs tree (suc n)))
    vecs-coh n = cong vec (sym (tree .Limit.is-lim n))
  toTraceFirstIso .inv (trace , vecs , vecs-coh) .elements n = #_ {length = trace .fst n} $ vecs n
  toTraceFirstIso .inv (trace , vecs , vecs-coh) .is-lim n = sym $ ΣVecPathP (trace .snd n) (vecs-coh n)
  toTraceFirstIso .rightInv _ = refl
  toTraceFirstIso .leftInv _ = refl

  module _ (sz : ) where
    open Limit
    open VecExt using (lookup-map ; lookup⁻¹ ; lookup-right-inv ; lookup-left-inv)

    vecChain : Chain _
    vecChain .Chain.Ob n = Vec (ΣVec ^ n) sz
    vecChain .Chain.π n = Vec.map (!^ n)

    toVecLimit : Iso (OverTrace (constTrace sz)) (Limit vecChain)
    toVecLimit .fun (vecs , vecs-coh) = lim vecs (sym  vecs-coh)
    toVecLimit .inv (lim elements is-lim) = elements , sym  is-lim
    toVecLimit .rightInv _ = refl
    toVecLimit .leftInv _ = refl

    toFinVecOfLimits : Iso (Limit vecChain) (FinVec (Lim ΣVec) sz)
    toFinVecOfLimits = go where
      module _ (vec : Limit vecChain) where
        f-elements : FinVec (∀ n  ΣVec ^ n) sz
        f-elements k = Vec.lookup k  (vec .elements)

        f-is-lim :  k  isTree (f-elements k)
        f-is-lim k n =
          (!^ n) (Vec.lookup k $ vec .elements (suc n))           ≡⟨ sym (VecExt.lookup-map (!^ n) (vec .elements (suc n)) k) 
          (Vec.lookup k $ Vec.map (!^ n) (vec .elements (suc n))) ≡⟨ cong (Vec.lookup k) (vec .is-lim n) ⟩∎
          (Vec.lookup k $ vec .elements n) 

        f : FinVec (Lim ΣVec) sz
        f k .elements = f-elements k
        f k .is-lim = f-is-lim k

      module _ (vec : FinVec (Lim ΣVec) sz) where
        f⁻¹-elements :  n  Vec (ΣVec ^ n) sz
        f⁻¹-elements n = Vec.map (cut n) (lookup⁻¹ vec)

        f⁻¹-is-lim : isLimit vecChain f⁻¹-elements
        f⁻¹-is-lim n =
          map (!^ n) (map (cut (suc n)) (lookup⁻¹ vec)) ≡⟨ sym (map-comp (!^ n) (cut (suc n)) _) 
          map (!^ n  (cut (suc n))) (lookup⁻¹ vec)     ≡⟨ cong  f  map f (lookup⁻¹ vec)) (TerminalChain.cut-is-lim ΣVec n) 
          map (cut n) (lookup⁻¹ vec) 

        f⁻¹ : Limit vecChain
        f⁻¹ .elements = f⁻¹-elements
        f⁻¹ .is-lim = f⁻¹-is-lim

      go : Iso _ _
      go .fun = f
      go .inv = f⁻¹
      go .rightInv vec-of-lim = funExt λ { k  isSet→LimitPathExt _ isSetΣVec^ (right-inv k) } where abstract
        right-inv :  k n  f-elements (f⁻¹ vec-of-lim) k n  vec-of-lim k .elements n
        right-inv k n =
          Vec.lookup k (Vec.map (cut n) (lookup⁻¹ vec-of-lim))  ≡⟨ VecExt.lookup-map (cut n) (lookup⁻¹ vec-of-lim) k 
          (cut n $ Vec.lookup k (lookup⁻¹ vec-of-lim))          ≡⟨ cong (cut n) (funExt⁻ (lookup-right-inv vec-of-lim) k) 
          (cut n $ vec-of-lim k) 
      go .leftInv lim-of-vec = isSet→LimitPathExt vecChain  k  VecExt.isSetVec (isSetΣVec^ k)) left-inv where abstract
        left-inv :  n  f⁻¹ (f lim-of-vec) .elements n  lim-of-vec .elements n
        left-inv n =
          Vec.map (cut n) (lookup⁻¹ (f lim-of-vec))                 ≡⟨ VecExt.lookup⁻¹-map (cut n) _ 
          lookup⁻¹ (cut n  f lim-of-vec)                           ≡⟨⟩
          lookup⁻¹  k  f-elements lim-of-vec k n)                ≡⟨⟩
          lookup⁻¹  k  (Vec.lookup k $ lim-of-vec .elements n))  ≡⟨ lookup-left-inv _ 
          lim-of-vec .elements n 

pres′⁺ : ΣVec (Lim ΣVec)  ShLim ΣVec
pres′⁺ = pres-Iso .inv

pres′⁺≡pres : pres′⁺  pres⁺
pres′⁺≡pres = funExt $ TerminalChain.isSet→ShLimPath ΣVec (isSetΣVec^  suc)  goal where
  open Limit
  goal :  (xs : ΣVec (Lim ΣVec)) n  pres-Iso .inv xs .elements n  pres⁺ xs .elements n
  goal xs n = ΣVecPathP refl $ cong (Vec.map (cut n)) (VecExt.lookup-left-inv (xs .vec))

abstract
  isLimitPreservingΣVec : isLimitPreserving ΣVec
  isLimitPreservingΣVec = subst isEquiv pres′⁺≡pres (isoToIsEquiv (invIso pres-Iso))

pres : ΣVec (Lim ΣVec)  ShLim ΣVec
pres = pres⁺ , isLimitPreservingΣVec

pres′ : ΣVec (Lim ΣVec)  ShLim ΣVec
pres′ = pres′⁺ , isoToIsEquiv (invIso pres-Iso)

pres′≡pres : pres′  pres
pres′≡pres = equivEq pres′⁺≡pres

open TerminalChain.Fixpoint isLimitPreservingΣVec
  using (fix ; fix⁺ ; fix⁻ ; fix⁻-step-ext ; pres⁻)
  public

ΣVecLimitEquiv : ΣVec (Lim ΣVec)  Lim ΣVec
ΣVecLimitEquiv = fix

ΣVecLimitPath : ΣVec (Lim ΣVec)  Lim ΣVec
ΣVecLimitPath = ua ΣVecLimitEquiv

isSetLimΣVec : isSet (Lim ΣVec)
isSetLimΣVec = isOfHLevelLimit _ 2 isSetΣVec^

isSetTree = isSetLimΣVec

open Limit

TreePath :  {l₁ l₂ : Lim ΣVec}  (∀ n  l₁ .elements n  l₂ .elements n)  l₁  l₂
TreePath = isSet→LimitPathExt _ isSetΣVec^

module _ {C : Type} (γ : C  ΣVec C) where
  step : (n : )  C  ΣVec ^ n
  step zero _ = tt*
  step (suc n) c = map (step n) (γ c)

  is-lim-step :  c  isLimit (TerminalChain.ch ΣVec) (flip step c)
  is-lim-step c zero = refl
  is-lim-step c (suc n) = goal where abstract
    goal : map (!^ n) (step (suc $ suc n) c)  step (suc n) c
    goal =
      (map (!^ n) $ map (step (suc n)) $ γ c) ≡⟨ sym (map-comp (!^ n) _ (γ c)) 
      (map (!^ n  step (suc n)) $ γ c)       ≡⟨ cong  f  map f (γ c)) (funExt λ c'  is-lim-step c' n) 
      (map (step n) $ γ c) 

  unfold : C  Lim ΣVec
  unfold c .elements = flip step c
  unfold c .is-lim = is-lim-step c

  abstract
    step-unfold :  c n  step n c  fix⁺ (map unfold (γ c)) .elements n
    step-unfold c zero = refl {x = tt*}
    step-unfold c (suc n) =
      map (step n) (γ c)                                      ≡⟨⟩
      map (cut n  unfold) (γ c)                              ≡⟨ map-comp (cut n) unfold _ 
      map (cut n)              (map unfold (γ c))             ≡⟨ cong-map-ext {{FunctorΣVec}} (sym $ TerminalChain.cut-is-lim _ n) _ 
      map (!^ n  cut (suc n)) (map unfold (γ c))             ≡⟨ map-comp (!^ n) (cut (suc n)) _ 
      map (!^ n) (map (cut (suc n)) $ map unfold (γ c))       ≡⟨⟩
      !^ (suc n) (pres⁺ (map unfold (γ c)) .elements (suc n))  ≡⟨⟩
      ShLim→Lim _ (pres⁺ (map unfold (γ c))) .elements (suc n) ≡⟨⟩
      fix⁺ (map unfold (γ c)) .elements (suc n) 

    unfold-fix⁺ :  c  unfold c  fix⁺ (map unfold (γ c))
    unfold-fix⁺ c = isSet→LimitPathExt _ isSetΣVec^ (step-unfold c)

  isCoalgebraMorphismUnfold : isCoalgebraMorphism ΣVec γ fix⁻ unfold
  isCoalgebraMorphismUnfold = funExt goal where abstract
    goal :  c  fix⁻ (unfold c)  BVec.map unfold (γ c)
    goal c =
      fix⁻ (unfold c) ≡⟨ cong fix⁻ (unfold-fix⁺ c) 
      fix⁻ (fix⁺ (BVec.map unfold (γ c))) ≡⟨ retEq ΣVecLimitEquiv (BVec.map unfold (γ c)) 
      map unfold (γ c) 

tree-width : Tree  
tree-width = length  fix⁻

isTerminalFix⁻ : isTerminal ΣVec fix⁻
isTerminalFix⁻ {B = B} β = ana , anaEq where
  open TerminalChain.Fixpoint isLimitPreservingΣVec
    using (fix⁺-step-ext)

  ana : CoalgebraMorphism ΣVec β fix⁻
  ana = unfold β , isCoalgebraMorphismUnfold β

  anaEq :  f  ana  f
  anaEq (f , f-is-mor) = Σ≡Prop isPropIsCoalgebraMorphism' eq where abstract
    isPropIsCoalgebraMorphism' : (u : B  Lim ΣVec)  isProp (isCoalgebraMorphism ΣVec β fix⁻ u)
    isPropIsCoalgebraMorphism' u = isSet→isPropIsCoalgebraMorphism ΣVec β fix⁻ u (BVec.isSetΣVec isSetLimΣVec)

    eq : unfold β  f
    eq = funExt $ TreePath  λ b n  funExt⁻ (goal n) b where
      goal :  n  step β n  cut n  f
      goal zero = refl
      goal (suc n) =
        map (step β n)  β        ≡⟨ cong  g  map g  β) (goal n) 
        map (cut n  f)  β       ≡⟨ cong (_∘ β) (map-comp-ext (cut n) f) 
        (map (cut n)  map f)  β ≡⟨ ∘-assoc (map (cut n)) (map f) β 
        map (cut n)  (map f  β) ≡⟨ cong (map (cut n) ∘_) (sym f-is-mor) 
        map (cut n)  (fix⁻  f)  ≡⟨ sym (∘-assoc (map (cut n)) fix⁻ f) 
        (map (cut n)  fix⁻)  f  ≡⟨ cong (_∘ f) (fix⁻-step-ext n) 
        cut (suc n)  f