\import Category
\import Category.Functor
\import Equiv
\import Function.Meta
\import Meta
\import Paths
\import Paths.Meta

\class RightAdjoint \extends Functor {
  | LAdj : Functor D C
  | eta : NatTrans Id (Comp \this LAdj)
  \field isAdjoint {X : D} {Y : C} : Equiv {Hom (LAdj X) Y} {Hom X (F Y)} (Func __  eta X)

  \func epsilon : NatTrans (Comp LAdj \this) Id \cowith
    | trans X => isAdjoint.ret (id _)
    | natural f => isAdjoint.isInj $ unfold $ rewrite (Func-o, Func-o, o-assoc, o-assoc, isAdjoint.f_ret, id-right, inv (eta.natural _), inv o-assoc, isAdjoint.f_ret) id-left

  \lemma eta_epsilon-left (Y : C) : Func (epsilon Y)  eta (F Y) = id (F Y)
    => isAdjoint.f_ret _

  \lemma eta_epsilon-right (X : D) : epsilon (LAdj X)  LAdj.Func (eta X) = id (LAdj X)
    => isAdjoint.isInj $ unfold $ rewrite (Func-id, Func-o, o-assoc, inv (eta.natural _), inv o-assoc, isAdjoint.f_ret) idp

  \lemma adjoint_epsilon {X : D} {Y : C} (f : Hom X (F Y)) : isAdjoint.ret f = epsilon Y  LAdj.Func f
    => isAdjoint.isInj $ unfold $ isAdjoint.f_ret _ *> rewrite (Func-o, o-assoc, inv (eta.natural _), inv o-assoc, isAdjoint.f_ret, id-left) idp

  \lemma eta_eps_equiv {X : D} {Y : C} : QEquiv {Hom (LAdj X) Y} {Hom X (F Y)} (Func __  eta X) (epsilon Y  LAdj.Func __) \cowith
    | ret_f f => inv (adjoint_epsilon _) *> isAdjoint.ret_f _
    | f_sec g => rewriteI adjoint_epsilon (isAdjoint.f_ret _)
} \where {
    \func rightFactor {C D E : Precat} (G1 : FullyFaithfulFunctor D E) (G2 : Functor E D) (adj : RightAdjoint { | Functor => Comp G1 G2 }) : RightAdjoint \cowith
      | Functor => G2
      | LAdj => Comp adj.LAdj G1
      | eta {
        | trans Y => G1.inverse (adj.eta (G1 Y))
        | natural h => run {
            G1.isFullyFaithful.isInj,
            repeat {2} (rewrite G1.Func-o),
            repeat {2} (rewrite G1.inverse-right),
            adj.eta.natural (G1.Func h)
          }
      }
      | isAdjoint => \new QEquiv {
        | ret h => adj.isAdjoint.ret (G1.Func h)
        | ret_f h => rewrite (G1.Func-o, G1.inverse-right) (adj.isAdjoint.ret_f _)
        | f_sec h => run {
            G1.isFullyFaithful.isInj,
            rewrite (G1.Func-o, G1.inverse-right),
            adj.isAdjoint.f_ret _
          }
      }
  }

\class CatEquiv \extends FullyFaithfulFunctor, RightAdjoint {
  \field eta-iso {X : D} : Iso (eta X)
  \field epsilon-iso {Y : C} : Iso (epsilon Y)
  | isFullyFaithful => TwoOutOfThree.rightFactor _ (symQEquiv isAdjoint) $ transport (QEquiv __) (ext \lam h => inv (adjoint_epsilon _ *> epsilon.natural h)) epsilon-iso.-o_Equiv

  \func op : CatEquiv \cowith
    | Functor => Functor.op
    | LAdj => LAdj.op
    | eta => NatTrans.op {eta.iso eta-iso}
    | isAdjoint {X} {Y} => \new QEquiv {
      | ret g => LAdj.Func g  epsilon-iso.hinv
      | ret_f f => run {
        rewrite LAdj.Func-o,
        rewriteEq (inv (natural {epsilon.iso epsilon-iso} f)),
        inv,
        Iso.adjoint {LAdj.Func-iso eta-iso},
        epsilon-iso.adjoint,
        rewriteEq (eta_epsilon-right X),
        id-left
      }
      | f_sec => unfold \lam g => run {
        rewrite Func-o,
        rewriteEq (natural {eta.iso eta-iso} g),
        inv,
        Iso.adjoint' {Func-iso epsilon-iso},
        eta-iso.adjoint',
        rewriteEq (eta_epsilon-left Y),
        id-right
      }
    }
    | eta-iso => eta-iso.op.reverse
    | epsilon-iso => unfold $ rewrite (LAdj.Func-id,id-left) epsilon-iso.op.reverse

  \func reverse : CatEquiv \cowith
    | Functor => LAdj
    | LAdj => \this
    | eta => epsilon.iso epsilon-iso
    | isAdjoint => \new QEquiv {
      | ret h => eta-iso.hinv  Func h
      | ret_f h => rewrite (Func-o, inv o-assoc, natural {eta.iso eta-iso}, o-assoc) $
                    pmap (h ) (pmap (`∘ _) (inv (Iso.adjoint' (eta_epsilon-left _) *> id-left )) *> f_hinv {Functor.Func-iso epsilon-iso}) *> id-right
      | f_sec h => rewrite (LAdj.Func-o, o-assoc, inv (natural {epsilon.iso epsilon-iso} _), inv o-assoc) $
                    pmap (`∘ h) (pmap (_ ) (inv (epsilon-iso.adjoint (eta_epsilon-right _) *> id-right)) *> hinv_f {LAdj.Func-iso eta-iso}) *> id-left
    }
    | eta-iso => epsilon-iso.reverse
    | epsilon-iso => unfold $ rewrite (Func-id,id-right) eta-iso.reverse
}