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

module Categories.Functor.Construction.Limit where

open import Function using (_$_)

open import Categories.Adjoint
open import Categories.Adjoint.Equivalence
open import Categories.Adjoint.Compose
open import Categories.Adjoint.Properties
open import Categories.Category
open import Categories.Category.Construction.Cones
open import Categories.Category.Complete
open import Categories.Category.Cocomplete
open import Categories.Category.Construction.Functors
open import Categories.Functor
open import Categories.Functor.Construction.Diagonal
open import Categories.Functor.Construction.Constant
open import Categories.NaturalTransformation renaming (id to idN)
open import Categories.NaturalTransformation.NaturalIsomorphism using (_≃_; module )
open import Categories.Diagram.Cone.Properties
open import Categories.Diagram.Duality

import Categories.Diagram.Limit as Lim
import Categories.Diagram.Cone as Con
import Categories.Morphism.Reasoning as MR

module _ {o  e o′ ℓ′ e′} (C : Category o  e) (Com : Complete o′ ℓ′ e′ C) {J : Category o′ ℓ′ e′} where
  private
    module C = Category C
    module J = Category J
    open C
    open HomReasoning
    open Lim.Limit
    open Con.Cone⇒ renaming (commute to ⇒-commute)
    open MR C

    K⇒lim :  {F G : Functor J C} (α : NaturalTransformation F G) (K : Con.Cone F) 
              Cones G [ nat-map-Cone α K , limit (Com G) ]
    K⇒lim {G = G} α K = rep-cone (Com G) (nat-map-Cone α K)

    lim⇒lim :  {F G : Functor J C} (α : NaturalTransformation F G) 
                Cones G [ nat-map-Cone α (limit (Com F)) , limit (Com G) ]
    lim⇒lim {F} α = K⇒lim α (limit (Com F))

    id-Cone :  X  Con.Cone {C = C} {J = J} (const X)
    id-Cone X = record
      { apex = record
        { ψ       = λ _  C.id
        ; commute = λ _  C.identity²
        }
      }

    X⇒limΔ :  X  Cones (const {C = J} X) [ id-Cone X , limit (Com (const X)) ]
    X⇒limΔ X = rep-cone (Com (const X)) _

  LimitF : Functor (Functors J C) C
  LimitF = record
    { F₀           = λ F  apex (Com F)
    ; F₁           = λ {F G} α  arr (lim⇒lim α)
    ; identity     = λ {F}  terminal.!-unique (Com F) $ record
      { arr     = C.id
      ; commute = id-comm
      }
    ; homomorphism = λ {F G H} {α β} 
      let module α = NaturalTransformation α
          module β = NaturalTransformation β
      in terminal.!-unique₂ (Com H) {_} {terminal.! (Com H)}
      {record { commute = λ {j}  begin
        proj (Com H) j  arr (lim⇒lim β)  arr (lim⇒lim α) ≈⟨ pullˡ (⇒-commute (lim⇒lim β)) 
        (β.η j  proj (Com G) j)  arr (lim⇒lim α)         ≈⟨ assoc 
        β.η j  proj (Com G) j  arr (lim⇒lim α)           ≈⟨ pushʳ (⇒-commute (lim⇒lim α)) 
        (β.η j  α.η j)  proj (Com F) j                    }}
    ; F-resp-≈     = λ {F G} {α β} eq  terminal.!-unique (Com G) $ record
      { commute = ⇒-commute (lim⇒lim β)  ∘-resp-≈ˡ ( eq)
      }
    }

  Δ⊣LimitF : ΔF J  LimitF
  Δ⊣LimitF = record
    { unit   = ntHelper record
      { η       = λ X  rep (Com (const X)) (id-Cone X)
      ; commute = λ {X Y} f  terminal.!-unique₂ (Com (const Y))
        {record
          { apex = record
            { ψ       = λ _  f
            ; commute = λ _  C.identityˡ
            }
          }}
        {record
          { commute = cancelˡ (⇒-commute (X⇒limΔ Y))
          }}
        {record
          { commute =
            ⇒-commute (Cones (const Y) [ lim⇒lim (constNat f)
                                        nat-map-Cone⇒ (constNat f) (terminal.! (Com (const X)) {id-Cone X}) ])
             identityʳ
          }}
      }
    ; counit = ntHelper record
      { η       = counit-nat
      ; commute = λ α  ⇒-commute (lim⇒lim α)
      }
    ; zig    = λ {X}  ⇒-commute (X⇒limΔ X)
    ; zag    = λ {F} 
      let apF = apex (Com F)
          align : Cones F [ limit (Com F) , nat-map-Cone (counit-nat F) (id-Cone apF) ]
          align = record
            { arr     = C.id
            ; commute = identityʳ  identityʳ
            }
          limF⇒limF : Cones F [ limit (Com F) , limit (Com F) ]
          limF⇒limF = Cones F [ Cones F [ lim⇒lim (counit-nat F)
                               nat-map-Cone⇒ (counit-nat F) (terminal.! (Com (const apF))) ]
                               align ]
      in terminal.!-unique₂ (Com F)
         {limit (Com F)}
         {record { commute = ∘-resp-≈ʳ ( identityʳ)  ⇒-commute limF⇒limF }}
         {record { commute = identityʳ }}
    }
    where counit-nat : (F : Functor J C)  NaturalTransformation (const (apex (Com F))) F
          counit-nat F = ntHelper record
            { η       = proj (Com F)
            ; commute = λ f  identityʳ   (limit-commute (Com F) f)
            }

module _ {o  e o′ ℓ′ e′} (C : Category o  e) (Coc : Cocomplete o′ ℓ′ e′ C) {J : Category o′ ℓ′ e′} where
  private
    module C = Category C
    module J = Category J
    open C
    open MR C

    Com : Complete o′ ℓ′ e′ C.op
    Com F = Colimit⇒coLimit C (Coc (Functor.op F))
    LF  : Functor (Functors J.op op) C.op
    LF    = LimitF C.op Com {J.op}

  ColimitF : Functor (Functors J C) C
  ColimitF = Functor.op LF ∘F opF⇐

  ColimitF⊣Δ : ColimitF  ΔF J
  ColimitF⊣Δ = ⊣×≃⇒⊣ helper ≃.refl ΔF≃
    where Δ⊣LimitFᵒᵖ : ΔF J.op  LF
          Δ⊣LimitFᵒᵖ = Δ⊣LimitF op Com {J.op}
          opF⊣ : opF⇐ {A = J} {B = C}  opF⇒
          opF⊣ = ⊣Equivalence.R⊣L (Functorsᵒᵖ-equiv J C)
          helper : ColimitF  opF⇒ ∘F Functor.op (ΔF J.op)
          helper = opF⊣ ∘⊣ Adjoint.op Δ⊣LimitFᵒᵖ
          ΔF≃ : opF⇒ ∘F Functor.op (ΔF J.op)  ΔF J
          ΔF≃ = record
            { F⇒G = record
              { η           = λ _  idN
              ; commute     = λ _  id-comm-sym
              ; sym-commute = λ _  id-comm
              }
            ; F⇐G = record
              { η           = λ _  idN
              ; commute     = λ _  id-comm-sym
              ; sym-commute = λ _  id-comm
              }
            ; iso = λ X  record
              { isoˡ = identity²
              ; isoʳ = identity²
              }
            }