\import Category
\import Category.Comma
\import Category.Functor
\import Category.Limit
\import Function.Meta
\import Meta
\import Paths
\import Paths.Meta

\func LeftKanExt {C C' : SmallPrecat} {D : CocompletePrecat} (p : Functor C C') (F : Functor C D) : Functor C' D
  => Functor.op {RightKanExt D.op p.op F.op}

\func RightKanExt {C C' : SmallPrecat} (D : CompletePrecat) (p : Functor C C') (F : Functor C D) : Functor C' D \cowith
   | F c' => lim F c'
   | Func {x} {y} f => limMap {lim F y} (Cone.premap (commaFunctor f) (lim F x))
   | Func-id {x} => limUniqueBeta {lim F x} {lim F x} \lam j => path (\lam i => coneMap {lim F x} (j.1, j.2, (id-right *> id-left) @ i)) *> inv id-right
   | Func-o {x} {y} {z} {g} {f} => limUniqueBeta {lim F z} {lim F x} \lam j => inv (pmap (`∘ _) (limBeta {lim F z} (Cone.premap (commaFunctor g) (lim F y)) j) *>
       limBeta {lim F y} (Cone.premap (commaFunctor f) (lim F x)) (j.1, j.2, id (p j.2)  j.3  g) *> path (\lam i => coneMap {lim F x} (j.1, j.2, (pmap (`∘ _) id-left *> o-assoc) @ i))) *> o-assoc
  \where {
    \open Limit

    \func lim {C C' : SmallPrecat} {D : CompletePrecat} {p : Functor C C'} (F : Functor C D) (b : C')
      => D.limit (Comp F (commaPrecat.rightForget (Const {TrivialCat} b) p))

    \func commaFunctor {C C' : SmallPrecat} {p : Functor C C'} {x y : C'} (f : Hom y x)
      => commaPrecat.functor (Const {TrivialCat} x) (Const y) p p (Const.natTrans f) (id p)

    -- | A limit of the form `lim_{j : J} (LeftKanExt.op D p F (G j))` is a double limit and can be expressed as a single limit of {F} over the comma category `G|p`.
    \class DoubleLimit \noclassifying {J C C' : SmallPrecat} {D : CompletePrecat} (p : Functor C C') (F : Functor C D) (G : Functor J C') {
      \lemma map_iso (c : Cone G) (e : Iso (lim'.limMap (map_cone c))) : Iso (limMap {D.limit (Comp (RightKanExt D p F) G)} (Cone.map (RightKanExt D p F) c))
        => Iso.rightFactor _ RightKanExt.DoubleLimit.iso $ transport (Iso __) (lim'.limUniqueBeta {_} {map_cone c} \lam k => inv (pmap (`∘ _) (lim'.limBeta cone k) *> o-assoc *> pmap (_ ) (limBeta (Cone.map (RightKanExt D p F) c) _) *>
              limBeta {limit (Comp F (commaPrecat.rightForget (Const (G k.1)) p))} (Cone.premap (commaFunctor (c.coneMap k.1)) (lim F c)) ((), k.2, k.3) *> repeat {2} unfold (rewrite id-left idp)) *> o-assoc) e

      \func map_cone (c : Cone G) : Cone (Comp F (commaPrecat.rightForget G p)) \cowith
        | apex => RightKanExt D p F c
        | coneMap (j,z,f) => coneMap $ later ((), z, f  c.coneMap j)
        | coneCoh {w} {w'} (h1,h2,h3) => coneCoh {D.limit (Comp F (commaPrecat.rightForget (Const c) p))} {(), w.2, w.3  c.coneMap w.1} {(), w'.2, w'.3  c.coneMap w'.1}
            (Graph.empty idp, h2, id-right *> pmap (w'.3 ) (inv (c.coneCoh h1)) *> inv o-assoc *> pmap (`∘ _) h3 *> o-assoc)

      \func iso : Iso {D} {D.limit (Comp (RightKanExt D p F) G)} {lim'} \cowith
        | f => lim'.limMap cone
        | hinv => limMap cone'
        | hinv_f => limUnique \lam j => inv o-assoc *> pmap (`∘ _) (limBeta cone' j) *> limUnique (\lam w => inv o-assoc *> pmap (`∘ _) (limBeta {lim F (G j)} (cone'' j) w) *> lim'.limBeta cone (j, w.2, w.3)) *> inv id-right
        | f_hinv => limUnique \lam w => inv o-assoc *> pmap (`∘ _) (lim'.limBeta cone w) *> o-assoc *> pmap (_ ) (limBeta {D.limit (Comp (RightKanExt D p F) G)} cone' w.1) *> limBeta {lim F (G w.1)} (cone'' w.1) ((), w.2, w.3) *> inv id-right

      \func lim' : Limit => D.limit (Comp F (commaPrecat.rightForget G p))

      \func cone : Cone (Comp F (commaPrecat.rightForget G p)) (D.limit (Comp (RightKanExt D p F) G)) \cowith
        | coneMap w => later (coneMap $ later ((), w.2, w.3))  coneMap (commaPrecat.leftForget G p w)
        | coneCoh {w} {w'} h => inv o-assoc *> pmap (`∘ _) (coneCoh {lim F (G w.1)} {(), w.2, w.3} {(), w'.2, id (p w'.2)  w'.3  G.Func h.1} (TrivialCat.id (), h.2, id-right *> o-assoc *> id-left *> h.3) *>
                                                            inv (limBeta {lim F (G w'.1)} (Cone.premap (commaFunctor (G.Func h.1)) (lim F (G w.1))) ((), w'.2, w'.3) )) *>
                                    o-assoc *> pmap (_ ) (coneCoh {D.limit (Comp (RightKanExt D p F) G)} h.1)

      \func cone'' (j : J) : Cone (Comp F (commaPrecat.rightForget (Const {TrivialCat} (G j)) p)) lim' \cowith
        | coneMap y => coneMap $ later (j, y.2, y.3)
        | coneCoh {y} {y'} h => lim'.coneCoh {j, y.2, y.3} {j, y'.2, y'.3} (id j, h.2, pmap (y'.3 ) (later Func-id) *> h.3)

      \func cone' : Cone (Comp (RightKanExt D p F) G) lim' \cowith
        | coneMap j => limMap {lim F (G j)} (cone'' j)
        | coneCoh {j} {j'} h => limUnique \lam w => inv o-assoc *> pmap (`∘ _) (limBeta {lim F (G j')} (Cone.premap (commaFunctor (G.Func h)) (lim F (G j))) w) *>
                                                      limBeta {lim F (G j)} (cone'' j) (w.1, w.2, id (p w.2)  w.3  G.Func h) *> inv (pmap (`∘ _) Func-id *> id-left) *>
                                                      lim'.coneCoh {j, w.2, id (p w.2)  w.3  G.Func h} {j', w.2, w.3} (h, id w.2, inv $ pmap (`∘ _) Func-id *> id-left *> o-assoc *> id-left) *>
                                                      inv (limBeta {lim F (G j')} (cone'' j') w)
    }
  }