\import Category
\import Category.Functor
\import Category.Limit
\import Category.Topos.Sheaf
\import Equiv (QEquiv)
\import Function.Meta
\import Meta
\import Paths
\import Paths.Meta
\import Set.SetHom

\func YonedaEmbedding {C : Precat} : FullyFaithfulFunctor
\cowith
  | Functor => functor {C}
  | isFullyFaithful {X} {Y} =>
    \new QEquiv {
      | ret => QEquiv.f {yoneda-lemma X (hom-presheaf Y)}
      | ret_f _ => id-right
      | f_sec => QEquiv.ret_f {yoneda-lemma X (hom-presheaf Y)}
    }
  \where {
    \func hom-presheaf {C : Precat} (c : C) : PresheafCat C => \new VPresheaf {
      | F => \new Functor {
        | F x => Hom x c
        | Func f g => g  f
        | Func-id => exts (\lam _ => id-right)
        | Func-o => exts (\lam _ => inv o-assoc)
      }
    }

    \func functor {C : Precat} : Functor C (PresheafCat C) \cowith
      | F => hom-presheaf
      | Func f => \new NatTrans {
        | trans _ => \lam g => f  g
        | natural _ => exts (\lam _ => inv $ o-assoc)
      }
      | Func-id => exts (\lam _ => exts (\lam _ => id-left))
      | Func-o => exts (\lam _ => exts (\lam _ => unfold $ o-assoc))

    \func yoneda-lemma {C : Precat} (A : C) (F : PresheafCat C)
      : QEquiv {Hom (Functor.F {functor} A) F} {F A} => \new QEquiv {
      | f nat => nat A (id A)
      | ret p => \new NatTrans {
        | trans _ f => Func {F} f p
        | natural _ => exts (\lam _ => unfold $ rewrite (Func-o {F}) idp)
      }
      | ret_f nf => exts (\lam _ => exts (\lam f =>
          \let | nat-nf => natural {nf} f
               | nat-nf-app => path (\lam i => nat-nf i (id A))
          \in
            unfold at nat-nf-app $ inv nat-nf-app *> rewrite id-left idp))
      | f_sec _ => rewrite (Func-id {F} {A}) idp
    }
  }

\instance Precategory-of-elements {C : Precat} (P : PresheafCat C) : Precat
  | Ob => \Sigma (c : Precat.Ob {C}) (p : P c)
  | Hom x y => \Sigma (u : Hom x.1 y.1) (x.2 = Func {P} u y.2)
  | id (c, p) => (id c, unfold $ rewrite (Func-id {P}) idp)
  | o (u, eq) (u', eq') => (u  u', eq' *> rewrite (eq, Func-o {P}) idp)
  | id-left => exts id-left
  | id-right => exts id-right
  | o-assoc => exts o-assoc
  \where {
    \func projection : Functor (Precategory-of-elements P) C
    \cowith
      | F (x, _) => x
      | Func (f, _) => f
      | Func-id => idp
      | Func-o => idp

    \func functorial {C : Precat} (P F : PresheafCat C) (nat : Hom P F)
      : Functor (Precategory-of-elements P) (Precategory-of-elements F)
    \cowith
      | F (c, p) => (c, nat c p)
      | Func {(c, p)} {(c', p')} (h, eq) => (h, rewrite eq $ path (\lam i => (natural {nat} h) i p'))
      | Func-id => exts $ idp
      | Func-o => exts $ idp
  }

\open YonedaEmbedding

\func presheaf-colimit {C : Precat} (P : PresheafCat C) :
  Colimit (Comp (functor {C}) (Precategory-of-elements.projection {C} {P})) => \new Limit {
  | apex => P
  | coneMap (c, p) => \new NatTrans {
    | trans _ h => Func {P} h p
    | natural _ => exts (\lam _ => unfold $ rewrite (Func-o {P}) idp)
  }
  | coneCoh {(c,p)} {(c', p')} (f , eq) => exts (\lam _ => exts (\lam _ => unfold $ rewrite eq (unfold $ rewrite (Func-o {P}) idp)))
  | limMap {G} cone =>
    \new NatTrans {
      | trans X p => QEquiv.f {yoneda-lemma _ G} (coneMap {cone} (X, p))
      | natural {X} {Y} f =>
        exts (\lam g =>
            \let | ch => coneCoh {cone} {X, g} {Y, Func {P} f g} (f, idp)
                 | nat => natural {coneMap {cone} (X, g)} f
                 | nat-applied => path (\lam i => (nat i) (id X))
            \in
              run {
                repeat {3} unfold,
                rewriteI ch,
                repeat {3} unfold,
                rewrite id-right,
                unfold, unfold,
                rewriteI nat-applied,
                unfold, rewrite id-left idp
              }
        )
    }
  | limBeta {Z} _ p0 =>
    \case\elim p0 \with {
      | (Y, p) => QEquiv.isInj {yoneda-lemma _ Z} $ unfold $ rewrite (Func-id {P}) idp
    }
  | limUnique eq => exts (\lam X =>
      exts (\lam p =>
          \let | elem : Precategory-of-elements P => (X, p)
               | eq-app => eq (X, p)
               | eq' => path (\lam i => (eq-app i) X)
               | eq'' => path (\lam i => (eq' i) (id X))
          \in
            (repeat {3} unfold at eq'') $ (rewriteI (path (\lam i => (Func-id {P} {X}) i p) : Func {P} {X} {X} (id X) p = p)) eq'')
  )
}
  \where {
    \func diagram-functor => Comp (functor {C}) (Precategory-of-elements.projection {C} {P})
  }

\open Precategory-of-elements

\func embedding-universal {C : SmallPrecat} {E : CocompleteCat \levels (\suc \lp) _}
                          (A : Functor C E)
  : \Sigma (L : Functor (PresheafCat C) E) (A = Comp L YonedaEmbedding) =>
  (L-Functor, Equiv.ret {FunctorCat.univalence {A} {Comp L-Functor YonedaEmbedding}} functors-iso)
  \where {
    \func functors-iso : Iso {FunctorCat {C} {E}} {A} {Comp L-Functor YonedaEmbedding}
    \cowith
      | f => nat
      | hinv => NatTrans.iso {nat} (\lam {X} => image-of-rep-iso X)
      | hinv_f => exts (\lam _ => unfold $ unfold $ Iso.hinv_f)
      | f_hinv => exts (\lam _ => unfold $ unfold $ Iso.f_hinv)
      \where {
        \func nat : Hom A (Comp L-Functor YonedaEmbedding) =>
          \new NatTrans {
            | trans x => image-of-rep-iso x
            | natural {X} {Y} f =>
              unfold $ Iso.adjointMap' $ rewrite o-assoc $ inv $ Iso.adjointMap $
                                                              limUnique {L-limit (hom-presheaf X)}
                                                                  (\lam p0 => \case\elim p0 \with {
                                                                    | (Z, p) =>
                                                                      \let
                                                                        | x-cone => image-of-representable X
                                                                        | p1 : Precategory-of-elements (hom-presheaf Y) => (Z,  unfold $ f  p)
                                                                        | cone-help => L-Functor.cone-in-induced {C} {E} {A} {hom-presheaf X} {hom-presheaf Y} (Func {YonedaEmbedding} f)
                                                                        | lim' => limBeta {L-limit (hom-presheaf X)} cone-help (Z, p)
                                                                        | help : Func {L-Functor} {hom-presheaf X} (Func {YonedaEmbedding} {X} f)  coneMap {L-limit (hom-presheaf X)} (Z, p) = coneMap {L-limit (hom-presheaf Y)} p1
                                                                        =>
                                                                          unfold (Func {L-Functor}) $ unfold L-Functor.induced-map $ unfold Limit.transFuncMap $
                                                                                                                                     unfold at lim' $ rewrite lim' $ rewrite id-left idp
                                                                      \in
                                                                        run {
                                                                          rewrite {2} o-assoc,
                                                                          rewrite (limBeta {L-limit (hom-presheaf X)} {A X} x-cone (Z, p)),
                                                                          unfold (coneMap {x-cone}),
                                                                          rewrite o-assoc ,
                                                                          rewrite help,
                                                                          rewrite (limBeta {L-limit (hom-presheaf Y)} {A Y} (image-of-representable Y) p1),
                                                                          unfold,
                                                                          rewrite (Func-o {A}),
                                                                          idp
                                                                        }
                                                                  }
                                                                  )
          }
      }

    \func diagram-functor (P : PresheafCat C) => Comp A (projection {C} {P})

    \func image-of-representable (x : C) : Colimit (diagram-functor (hom-presheaf x)) { | apex => A x }
    \cowith
      | coneMap p0 => \case\elim p0 \with {
        | (c, p) => Func {A} p
      }
      | coneCoh {i} {j} h =>\case\elim i, \elim j, \elim h \with {
        | (c, p), (c', p'), (f, eq) => rewriteI (Func-o {A}, eq) idp
      }
      | limMap cone => coneMap {cone} (x, id x)
      | limBeta cone j => \case\elim j \with {
        | (c, p) => coneCoh {cone} {x, id x} {c, p} (p, inv id-left)
      }
      | limUnique coh => unfold at coh $ \let | coh-app => coh (x, id x) \in rewrite (Func-id {A}, id-right, id-right) at coh-app $ coh-app

    \func image-of-rep-iso-op (x : C) : Iso {E.op} {A x} {L-limit (hom-presheaf x)} =>
      Limit.lim_iso {_} {_} {Functor.op {diagram-functor (hom-presheaf x)}} (L-limit $ hom-presheaf x) (image-of-representable x)

    \func image-of-rep-iso (x : C) : Iso {E} {A x} {L-limit (hom-presheaf x)} =>
      Iso.reverse {Iso.op {image-of-rep-iso-op x}}

    \func image-of-rep-eq (x : C) : A x = Limit.apex {L-limit (hom-presheaf x)} => E.univalence.ret $ image-of-rep-iso x

    \func L-limit (P : PresheafCat C) => E.colimit (diagram-functor P)

    \func L-Functor : Functor (PresheafCat C) E
    \cowith
      | F P => L-limit P
      | Func f => induced-map f
      | Func-id {X} =>
        Limit.limUniqueBeta {L-limit X} {L-limit X} (\lam p0 => \case\elim p0 \with {
          | (c, p) => repeat {2} unfold $ rewrite (id-right, id-left {_} {_} {L-limit X}) $ idp
        })
      | Func-o {X} {Y} {Z} {g} {f} =>
        unfold_let $ Limit.limUniqueBeta {L-limit X} {L-limit Z} (\lam j => \case\elim j \with {
          | (c, p) => repeat {6} unfold
            $ id-right *> (
                            \let
                              | limBeta1 => limBeta {L-limit X} (cone-in-induced f) (c, p)
                              | limBeta2 => limBeta {L-limit Y} (cone-in-induced g) (c, f c p)
                            \in
                              unfold at limBeta1 $ rewrite (o-assoc, limBeta1, id-left) $
                                                   unfold at limBeta2 $ rewrite (limBeta2, id-left) idp)
        })
      \where {
        \func induced-natural {X Y : PresheafCat C} (f : Hom X Y) :
          NatTrans (Comp (Functor.op {diagram-functor Y}) (Functor.op {functorial X Y f})) (Functor.op {diagram-functor X})
        \cowith
          | trans (q, a) => id (A q)
          | natural _ => repeat {4} unfold $ rewrite (id-left, id-right) idp

        \func induced-map {X Y : PresheafCat C} (f : Hom X Y) : Hom (L-limit X) (L-limit Y) =>
          Limit.transFuncMap (L-limit X) (L-limit Y) (Functor.op {functorial X Y f}) $ induced-natural f

        \func cone-in-induced {X Y : PresheafCat C} (f : Hom X Y) =>
          Limit.transFuncMap.cone {_} {L-limit X} {L-limit Y} {Functor.op {functorial X Y f}}
              {induced-natural f}
      }
  }