/-
Copyright (c) 2018 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Reid Barton, Mario Carneiro, Scott Morrison, Floris van Doorn

! This file was ported from Lean 3 source module category_theory.limits.has_limits
! leanprover-community/mathlib commit 2738d2ca56cbc63be80c3bd48e9ed90ad94e947d
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.CategoryTheory.Limits.IsLimit
import Mathlib.CategoryTheory.Category.ULift

/-!
# Existence of limits and colimits

In `CategoryTheory.Limits.IsLimit` we defined `IsLimit c`,
the data showing that a cone `c` is a limit cone.

The two main structures defined in this file are:
* `LimitCone F`, which consists of a choice of cone for `F` and the fact it is a limit cone, and
* `HasLimit F`, asserting the mere existence of some limit cone for `F`.

`HasLimit` is a propositional typeclass
(it's important that it is a proposition merely asserting the existence of a limit,
as otherwise we would have non-defeq problems from incompatible instances).

While `HasLimit` only asserts the existence of a limit cone,
we happily use the axiom of choice in mathlib,
so there are convenience functions all depending on `HasLimit F`:
* `limit F : C`, producing some limit object (of course all such are isomorphic)
* `limit.π F j : limit F ⟶ F.obj j`, the morphisms out of the limit,
* `limit.lift F c : c.pt ⟶ limit F`, the universal morphism from any other `c : Cone F`, etc.

Key to using the `HasLimit` interface is that there is an `@[ext]` lemma stating that
to check `f = g`, for `f g : Z ⟶ limit F`, it suffices to check `f ≫ limit.π F j = g ≫ limit.π F j`
for every `j`.
This, combined with `@[simp]` lemmas, makes it possible to prove many easy facts about limits using
automation (e.g. `tidy`).

There are abbreviations `HasLimitsOfShape J C` and `HasLimits C`
asserting the existence of classes of limits.
Later more are introduced, for finite limits, special shapes of limits, etc.

Ideally, many results about limits should be stated first in terms of `IsLimit`,
and then a result in terms of `HasLimit` derived from this.
At this point, however, this is far from uniformly achieved in mathlib ---
often statements are only written in terms of `HasLimit`.

## Implementation
At present we simply say everything twice, in order to handle both limits and colimits.
It would be highly desirable to have some automation support,
e.g. a `@[dualize]` attribute that behaves similarly to `@[to_additive]`.

## References
* [Stacks: Limits and colimits](https://stacks.math.columbia.edu/tag/002D)

-/

noncomputable section

open CategoryTheory CategoryTheory.Category CategoryTheory.Functor Opposite

namespace CategoryTheory.Limits

-- morphism levels before object levels. See note [CategoryTheory universes].
universe v₁ u₁ v₂ u₂ v₃ u₃ v v' v'' u u' u''

variable {J : Type u₁} [Category.{v₁} J] {K : Type u₂} [Category.{v₂} K]

variable {C : Type u} [Category.{v} C]

variable {F : J ⥤ C}

section Limit

/-- `LimitCone F` contains a cone over `F` together with the information that it is a limit. -/
-- @[nolint has_nonempty_instance] -- Porting note: removed
structure LimitCone (F : J ⥤ C) where
  /-- The cone itself -/
  cone : Cone F
  /-- The proof that is the limit cone -/
  isLimit : IsLimit cone
#align category_theory.limits.limit_cone CategoryTheory.Limits.LimitCone
#align category_theory.limits.limit_cone.is_limit CategoryTheory.Limits.LimitCone.isLimit

/-- `HasLimit F` represents the mere existence of a limit for `F`. -/
class HasLimit (F : J ⥤ C) : Prop where mk' ::
  /-- There is some limit cone for `F` -/
  exists_limit : Nonempty (LimitCone F)
#align category_theory.limits.has_limit CategoryTheory.Limits.HasLimit

theorem HasLimit.mk {F : J ⥤ C} (d : LimitCone F) : HasLimit F :=
  ⟨Nonempty.intro d⟩
#align category_theory.limits.has_limit.mk CategoryTheory.Limits.HasLimit.mk

/-- Use the axiom of choice to extract explicit `LimitCone F` from `HasLimit F`. -/
def getLimitCone (F : J ⥤ C) [HasLimit F] : LimitCone F :=
  Classical.choice <| HasLimit.exists_limit
#align category_theory.limits.get_limit_cone CategoryTheory.Limits.getLimitCone

variable (J C)

/-- `C` has limits of shape `J` if there exists a limit for every functor `F : J ⥤ C`. -/
class HasLimitsOfShape : Prop where
  /-- All functors `F : J ⥤  C` from `J` have limits -/
  has_limit : ∀ F : J ⥤ C, HasLimit F := by infer_instance
#align category_theory.limits.has_limits_of_shape CategoryTheory.Limits.HasLimitsOfShape

/-- `C` has all limits of size `v₁ u₁` (`HasLimitsOfSize.{v₁ u₁} C`)
if it has limits of every shape `J : Type u₁` with `[Category.{v₁} J]`.
-/
class HasLimitsOfSize (C : Type u) [Category.{v} C] : Prop where
  /-- All functors `F : J ⥤ C` from all small `J` have limits -/
  has_limits_of_shape : ∀ (J : Type u₁) [Category.{v₁} J], HasLimitsOfShape J C := by
    infer_instance
#align category_theory.limits.has_limits_of_size CategoryTheory.Limits.HasLimitsOfSize

/-- `C` has all (small) limits if it has limits of every shape that is as big as its hom-sets. -/
abbrev HasLimits (C : Type u) [Category.{v} C] : Prop :=
  HasLimitsOfSize.{v, v} C
#align category_theory.limits.has_limits CategoryTheory.Limits.HasLimits

theorem HasLimits.has_limits_of_shape {C : Type u} [Category.{v} C] [HasLimits C] (J : Type v)
    [Category.{v} J] : HasLimitsOfShape J C :=
  HasLimitsOfSize.has_limits_of_shape J
#align category_theory.limits.has_limits.has_limits_of_shape CategoryTheory.Limits.HasLimits.has_limits_of_shape

variable {J C}

-- see Note [lower instance priority]
instance (priority := 100) hasLimitOfHasLimitsOfShape {J : Type u₁} [Category.{v₁} J]
    [HasLimitsOfShape J C] (F : J ⥤ C) : HasLimit F :=
  HasLimitsOfShape.has_limit F
#align category_theory.limits.has_limit_of_has_limits_of_shape CategoryTheory.Limits.hasLimitOfHasLimitsOfShape

-- see Note [lower instance priority]
instance (priority := 100) hasLimitsOfShapeOfHasLimits {J : Type u₁} [Category.{v₁} J]
    [HasLimitsOfSize.{v₁, u₁} C] : HasLimitsOfShape J C :=
  HasLimitsOfSize.has_limits_of_shape J
#align category_theory.limits.has_limits_of_shape_of_has_limits CategoryTheory.Limits.hasLimitsOfShapeOfHasLimits

-- Interface to the `HasLimit` class.
/-- An arbitrary choice of limit cone for a functor. -/
def limit.cone (F : J ⥤ C) [HasLimit F] : Cone F :=
  (getLimitCone F).cone
#align category_theory.limits.limit.cone CategoryTheory.Limits.limit.cone

/-- An arbitrary choice of limit object of a functor. -/
def limit (F : J ⥤ C) [HasLimit F] :=
  (limit.cone F).pt
#align category_theory.limits.limit CategoryTheory.Limits.limit

/-- The projection from the limit object to a value of the functor. -/
def limit.π (F : J ⥤ C) [HasLimit F] (j : J) : limit F ⟶ F.obj j :=
  (limit.cone F).π.app j
#align category_theory.limits.limit.π CategoryTheory.Limits.limit.π

@[simp]
theorem limit.cone_x {F : J ⥤ C} [HasLimit F] : (limit.cone F).pt = limit F :=
  rfl
set_option linter.uppercaseLean3 false in
#align category_theory.limits.limit.cone_X CategoryTheory.Limits.limit.cone_x

@[simp]
theorem limit.cone_π {F : J ⥤ C} [HasLimit F] : (limit.cone F).π.app = limit.π _ :=
  rfl
#align category_theory.limits.limit.cone_π CategoryTheory.Limits.limit.cone_π

@[reassoc (attr := simp)]
theorem limit.w (F : J ⥤ C) [HasLimit F] {j j' : J} (f : j ⟶ j') :
    limit.π F j ≫ F.map f = limit.π F j' :=
  (limit.cone F).w f
#align category_theory.limits.limit.w CategoryTheory.Limits.limit.w

/-- Evidence that the arbitrary choice of cone provied by `limit.cone F` is a limit cone. -/
def limit.isLimit (F : J ⥤ C) [HasLimit F] : IsLimit (limit.cone F) :=
  (getLimitCone F).isLimit
#align category_theory.limits.limit.is_limit CategoryTheory.Limits.limit.isLimit

/-- The morphism from the cone point of any other cone to the limit object. -/
def limit.lift (F : J ⥤ C) [HasLimit F] (c : Cone F) : c.pt ⟶ limit F :=
  (limit.isLimit F).lift c
#align category_theory.limits.limit.lift CategoryTheory.Limits.limit.lift

@[simp]
theorem limit.isLimit_lift {F : J ⥤ C} [HasLimit F] (c : Cone F) :
    (limit.isLimit F).lift c = limit.lift F c :=
  rfl
#align category_theory.limits.limit.is_limit_lift CategoryTheory.Limits.limit.isLimit_lift

@[reassoc (attr := simp)]
theorem limit.lift_π {F : J ⥤ C} [HasLimit F] (c : Cone F) (j : J) :
    limit.lift F c ≫ limit.π F j = c.π.app j :=
  IsLimit.fac _ c j
#align category_theory.limits.limit.lift_π CategoryTheory.Limits.limit.lift_π

/-- Functoriality of limits.

Usually this morphism should be accessed through `lim.map`,
but may be needed separately when you have specified limits for the source and target functors,
but not necessarily for all functors of shape `J`.
-/
def limMap {F G : J ⥤ C} [HasLimit F] [HasLimit G] (α : F ⟶ G) : limit F ⟶ limit G :=
  IsLimit.map _ (limit.isLimit G) α
#align category_theory.limits.lim_map CategoryTheory.Limits.limMap

@[reassoc (attr := simp)]
theorem limMap_π {F G : J ⥤ C} [HasLimit F] [HasLimit G] (α : F ⟶ G) (j : J) :
    limMap α ≫ limit.π G j = limit.π F j ≫ α.app j :=
  limit.lift_π _ j
#align category_theory.limits.lim_map_π CategoryTheory.Limits.limMap_π

/-- The cone morphism from any cone to the arbitrary choice of limit cone. -/
def limit.coneMorphism {F : J ⥤ C} [HasLimit F] (c : Cone F) : c ⟶ limit.cone F :=
  (limit.isLimit F).liftConeMorphism c
#align category_theory.limits.limit.cone_morphism CategoryTheory.Limits.limit.coneMorphism

@[simp]
theorem limit.coneMorphism_hom {F : J ⥤ C} [HasLimit F] (c : Cone F) :
    (limit.coneMorphism c).Hom = limit.lift F c :=
  rfl
#align category_theory.limits.limit.cone_morphism_hom CategoryTheory.Limits.limit.coneMorphism_hom

theorem limit.coneMorphism_π {F : J ⥤ C} [HasLimit F] (c : Cone F) (j : J) :
    (limit.coneMorphism c).Hom ≫ limit.π F j = c.π.app j := by simp
#align category_theory.limits.limit.cone_morphism_π CategoryTheory.Limits.limit.coneMorphism_π

@[reassoc (attr := simp)]
theorem limit.conePointUniqueUpToIso_hom_comp {F : J ⥤ C} [HasLimit F] {c : Cone F} (hc : IsLimit c)
    (j : J) : (IsLimit.conePointUniqueUpToIso hc (limit.isLimit _)).hom ≫ limit.π F j = c.π.app j :=
  IsLimit.conePointUniqueUpToIso_hom_comp _ _ _
#align category_theory.limits.limit.cone_point_unique_up_to_iso_hom_comp CategoryTheory.Limits.limit.conePointUniqueUpToIso_hom_comp

@[reassoc (attr := simp)]
theorem limit.conePointUniqueUpToIso_inv_comp {F : J ⥤ C} [HasLimit F] {c : Cone F} (hc : IsLimit c)
    (j : J) : (IsLimit.conePointUniqueUpToIso (limit.isLimit _) hc).inv ≫ limit.π F j = c.π.app j :=
  IsLimit.conePointUniqueUpToIso_inv_comp _ _ _
#align category_theory.limits.limit.cone_point_unique_up_to_iso_inv_comp CategoryTheory.Limits.limit.conePointUniqueUpToIso_inv_comp

theorem limit.existsUnique {F : J ⥤ C} [HasLimit F] (t : Cone F) :
    ∃! l : t.pt ⟶ limit F, ∀ j, l ≫ limit.π F j = t.π.app j :=
  (limit.isLimit F).existsUnique _
#align category_theory.limits.limit.exists_unique CategoryTheory.Limits.limit.existsUnique

/-- Given any other limit cone for `F`, the chosen `limit F` is isomorphic to the cone point.
-/
def limit.isoLimitCone {F : J ⥤ C} [HasLimit F] (t : LimitCone F) : limit F ≅ t.cone.pt :=
  IsLimit.conePointUniqueUpToIso (limit.isLimit F) t.isLimit
#align category_theory.limits.limit.iso_limit_cone CategoryTheory.Limits.limit.isoLimitCone

@[reassoc (attr := simp)]
theorem limit.isoLimitCone_hom_π {F : J ⥤ C} [HasLimit F] (t : LimitCone F) (j : J) :
    (limit.isoLimitCone t).hom ≫ t.cone.π.app j = limit.π F j := by
  dsimp [limit.isoLimitCone, IsLimit.conePointUniqueUpToIso]
  aesop_cat
#align category_theory.limits.limit.iso_limit_cone_hom_π CategoryTheory.Limits.limit.isoLimitCone_hom_π

@[reassoc (attr := simp)]
theorem limit.isoLimitCone_inv_π {F : J ⥤ C} [HasLimit F] (t : LimitCone F) (j : J) :
    (limit.isoLimitCone t).inv ≫ limit.π F j = t.cone.π.app j := by
  dsimp [limit.isoLimitCone, IsLimit.conePointUniqueUpToIso]
  aesop_cat
#align category_theory.limits.limit.iso_limit_cone_inv_π CategoryTheory.Limits.limit.isoLimitCone_inv_π

@[ext]
theorem limit.hom_ext {F : J ⥤ C} [HasLimit F] {X : C} {f f' : X ⟶ limit F}
    (w : ∀ j, f ≫ limit.π F j = f' ≫ limit.π F j) : f = f' :=
  (limit.isLimit F).hom_ext w
#align category_theory.limits.limit.hom_ext CategoryTheory.Limits.limit.hom_ext

@[simp]
theorem limit.lift_map {F G : J ⥤ C} [HasLimit F] [HasLimit G] (c : Cone F) (α : F ⟶ G) :
    limit.lift F c ≫ limMap α = limit.lift G ((Cones.postcompose α).obj c) := by
  ext
  rw [assoc, limMap_π, limit.lift_π_assoc, limit.lift_π]
  rfl
#align category_theory.limits.limit.lift_map CategoryTheory.Limits.limit.lift_map

@[simp]
theorem limit.lift_cone {F : J ⥤ C} [HasLimit F] : limit.lift F (limit.cone F) = 𝟙 (limit F) :=
  (limit.isLimit _).lift_self
#align category_theory.limits.limit.lift_cone CategoryTheory.Limits.limit.lift_cone

/-- The isomorphism (in `Type`) between
morphisms from a specified object `W` to the limit object,
and cones with cone point `W`.
-/
def limit.homIso (F : J ⥤ C) [HasLimit F] (W : C) :
    ULift.{u₁} (W ⟶ limit F : Type v) ≅ F.cones.obj (op W) :=
  (limit.isLimit F).homIso W
#align category_theory.limits.limit.hom_iso CategoryTheory.Limits.limit.homIso

@[simp]
theorem limit.homIso_hom (F : J ⥤ C) [HasLimit F] {W : C} (f : ULift (W ⟶ limit F)) :
    (limit.homIso F W).hom f = (const J).map f.down ≫ (limit.cone F).π :=
  (limit.isLimit F).homIso_hom f
#align category_theory.limits.limit.hom_iso_hom CategoryTheory.Limits.limit.homIso_hom

/-- The isomorphism (in `Type`) between
morphisms from a specified object `W` to the limit object,
and an explicit componentwise description of cones with cone point `W`.
-/
def limit.homIso' (F : J ⥤ C) [HasLimit F] (W : C) :
    ULift.{u₁} (W ⟶ limit F : Type v) ≅
      { p : ∀ j, W ⟶ F.obj j // ∀ {j j' : J} (f : j ⟶ j'), p j ≫ F.map f = p j' } :=
  (limit.isLimit F).homIso' W
#align category_theory.limits.limit.hom_iso' CategoryTheory.Limits.limit.homIso'

theorem limit.lift_extend {F : J ⥤ C} [HasLimit F] (c : Cone F) {X : C} (f : X ⟶ c.pt) :
    limit.lift F (c.extend f) = f ≫ limit.lift F c := by aesop_cat
#align category_theory.limits.limit.lift_extend CategoryTheory.Limits.limit.lift_extend

/-- If a functor `F` has a limit, so does any naturally isomorphic functor.
-/
theorem hasLimitOfIso {F G : J ⥤ C} [HasLimit F] (α : F ≅ G) : HasLimit G :=
  HasLimit.mk
    { cone := (Cones.postcompose α.hom).obj (limit.cone F)
      isLimit :=
        { lift := fun s => limit.lift F ((Cones.postcompose α.inv).obj s)
          fac := fun s j =>
            by
            rw [Cones.postcompose_obj_π, NatTrans.comp_app, limit.cone_π, ← Category.assoc,
              limit.lift_π]
            simp
          uniq := fun s m w => by
            apply limit.hom_ext; intro j
            rw [limit.lift_π, Cones.postcompose_obj_π, NatTrans.comp_app, ← NatIso.app_inv,
              Iso.eq_comp_inv]
            simpa using w j } }
#align category_theory.limits.has_limit_of_iso CategoryTheory.Limits.hasLimitOfIso

-- See the construction of limits from products and equalizers
-- for an example usage.
/-- If a functor `G` has the same collection of cones as a functor `F`
which has a limit, then `G` also has a limit. -/
theorem HasLimit.ofConesIso {J K : Type u₁} [Category.{v₁} J] [Category.{v₂} K] (F : J ⥤ C)
    (G : K ⥤ C) (h : F.cones ≅ G.cones) [HasLimit F] : HasLimit G :=
  HasLimit.mk ⟨_, IsLimit.ofNatIso (IsLimit.natIso (limit.isLimit F) ≪≫ h)⟩
#align category_theory.limits.has_limit.of_cones_iso CategoryTheory.Limits.HasLimit.ofConesIso

/-- The limits of `F : J ⥤ C` and `G : J ⥤ C` are isomorphic,
if the functors are naturally isomorphic.
-/
def HasLimit.isoOfNatIso {F G : J ⥤ C} [HasLimit F] [HasLimit G] (w : F ≅ G) : limit F ≅ limit G :=
  IsLimit.conePointsIsoOfNatIso (limit.isLimit F) (limit.isLimit G) w
#align category_theory.limits.has_limit.iso_of_nat_iso CategoryTheory.Limits.HasLimit.isoOfNatIso

@[reassoc (attr := simp)]
theorem HasLimit.isoOfNatIso_hom_π {F G : J ⥤ C} [HasLimit F] [HasLimit G] (w : F ≅ G) (j : J) :
    (HasLimit.isoOfNatIso w).hom ≫ limit.π G j = limit.π F j ≫ w.hom.app j :=
  IsLimit.conePointsIsoOfNatIso_hom_comp _ _ _ _
#align category_theory.limits.has_limit.iso_of_nat_iso_hom_π CategoryTheory.Limits.HasLimit.isoOfNatIso_hom_π

@[reassoc (attr := simp)]
theorem HasLimit.isoOfNatIso_inv_π {F G : J ⥤ C} [HasLimit F] [HasLimit G] (w : F ≅ G) (j : J) :
    (HasLimit.isoOfNatIso w).inv ≫ limit.π F j = limit.π G j ≫ w.inv.app j :=
  IsLimit.conePointsIsoOfNatIso_inv_comp _ _ _ _
#align category_theory.limits.has_limit.iso_of_nat_iso_inv_π CategoryTheory.Limits.HasLimit.isoOfNatIso_inv_π

@[reassoc (attr := simp)]
theorem HasLimit.lift_isoOfNatIso_hom {F G : J ⥤ C} [HasLimit F] [HasLimit G] (t : Cone F)
    (w : F ≅ G) :
    limit.lift F t ≫ (HasLimit.isoOfNatIso w).hom =
      limit.lift G ((Cones.postcompose w.hom).obj _) :=
  IsLimit.lift_comp_conePointsIsoOfNatIso_hom _ _ _
#align category_theory.limits.has_limit.lift_iso_of_nat_iso_hom CategoryTheory.Limits.HasLimit.lift_isoOfNatIso_hom

@[reassoc (attr := simp)]
theorem HasLimit.lift_isoOfNatIso_inv {F G : J ⥤ C} [HasLimit F] [HasLimit G] (t : Cone G)
    (w : F ≅ G) :
    limit.lift G t ≫ (HasLimit.isoOfNatIso w).inv =
      limit.lift F ((Cones.postcompose w.inv).obj _) :=
  IsLimit.lift_comp_conePointsIsoOfNatIso_inv _ _ _
#align category_theory.limits.has_limit.lift_iso_of_nat_iso_inv CategoryTheory.Limits.HasLimit.lift_isoOfNatIso_inv

/-- The limits of `F : J ⥤ C` and `G : K ⥤ C` are isomorphic,
if there is an equivalence `e : J ≌ K` making the triangle commute up to natural isomorphism.
-/
def HasLimit.isoOfEquivalence {F : J ⥤ C} [HasLimit F] {G : K ⥤ C} [HasLimit G] (e : J ≌ K)
    (w : e.functor ⋙ G ≅ F) : limit F ≅ limit G :=
  IsLimit.conePointsIsoOfEquivalence (limit.isLimit F) (limit.isLimit G) e w
#align category_theory.limits.has_limit.iso_of_equivalence CategoryTheory.Limits.HasLimit.isoOfEquivalence

@[simp]
theorem HasLimit.isoOfEquivalence_hom_π {F : J ⥤ C} [HasLimit F] {G : K ⥤ C} [HasLimit G]
    (e : J ≌ K) (w : e.functor ⋙ G ≅ F) (k : K) :
    (HasLimit.isoOfEquivalence e w).hom ≫ limit.π G k =
      limit.π F (e.inverse.obj k) ≫ w.inv.app (e.inverse.obj k) ≫ G.map (e.counit.app k) := by
  simp only [HasLimit.isoOfEquivalence, IsLimit.conePointsIsoOfEquivalence_hom]
  dsimp
  simp
#align category_theory.limits.has_limit.iso_of_equivalence_hom_π CategoryTheory.Limits.HasLimit.isoOfEquivalence_hom_π

@[simp]
theorem HasLimit.isoOfEquivalence_inv_π {F : J ⥤ C} [HasLimit F] {G : K ⥤ C} [HasLimit G]
    (e : J ≌ K) (w : e.functor ⋙ G ≅ F) (j : J) :
    (HasLimit.isoOfEquivalence e w).inv ≫ limit.π F j = limit.π G (e.functor.obj j) ≫ w.hom.app j :=
  by
  simp only [HasLimit.isoOfEquivalence, IsLimit.conePointsIsoOfEquivalence_hom]
  dsimp
  simp
#align category_theory.limits.has_limit.iso_of_equivalence_inv_π CategoryTheory.Limits.HasLimit.isoOfEquivalence_inv_π

section Pre

variable (F) [HasLimit F] (E : K ⥤ J) [HasLimit (E ⋙ F)]

/-- The canonical morphism from the limit of `F` to the limit of `E ⋙ F`.
-/
def limit.pre : limit F ⟶ limit (E ⋙ F) :=
  limit.lift (E ⋙ F) ((limit.cone F).whisker E)
#align category_theory.limits.limit.pre CategoryTheory.Limits.limit.pre

@[reassoc (attr := simp)]

@[simp]
theorem limit.lift_pre (c : Cone F) :
    limit.lift F c ≫ limit.pre F E = limit.lift (E ⋙ F) (c.whisker E) := by ext; simp
#align category_theory.limits.limit.lift_pre CategoryTheory.Limits.limit.lift_pre

variable {L : Type u₃} [Category.{v₃} L]

variable (D : L ⥤ K) [HasLimit (D ⋙ E ⋙ F)]

@[simp]
theorem limit.pre_pre [h : HasLimit (D ⋙  E ⋙  F)] : haveI : HasLimit ((D ⋙  E) ⋙  F) := h;
    limit.pre F E ≫ limit.pre (E ⋙ F) D = limit.pre F (D ⋙ E) := by
  haveI : HasLimit ((D ⋙  E) ⋙  F) := h
  ext j; erw [assoc, limit.pre_π, limit.pre_π, limit.pre_π]; rfl
#align category_theory.limits.limit.pre_pre CategoryTheory.Limits.limit.pre_pre

variable {E F}

/-- -
If we have particular limit cones available for `E ⋙ F` and for `F`,
we obtain a formula for `limit.pre F E`.
-/
theorem limit.pre_eq (s : LimitCone (E ⋙ F)) (t : LimitCone F) :
    limit.pre F E =
      (limit.isoLimitCone t).hom ≫ s.isLimit.lift (t.cone.whisker E) ≫ (limit.isoLimitCone s).inv :=
  by aesop_cat
#align category_theory.limits.limit.pre_eq CategoryTheory.Limits.limit.pre_eq

end Pre

section Post

variable {D : Type u'} [Category.{v'} D]

variable (F) [HasLimit F] (G : C ⥤ D) [HasLimit (F ⋙ G)]

/-- The canonical morphism from `G` applied to the limit of `F` to the limit of `F ⋙ G`.
-/
def limit.post : G.obj (limit F) ⟶ limit (F ⋙ G) :=
  limit.lift (F ⋙ G) (G.mapCone (limit.cone F))
#align category_theory.limits.limit.post CategoryTheory.Limits.limit.post

@[reassoc (attr := simp)]
theorem limit.post_π (j : J) : limit.post F G ≫ limit.π (F ⋙ G) j = G.map (limit.π F j) := by
  erw [IsLimit.fac]
  rfl
#align category_theory.limits.limit.post_π CategoryTheory.Limits.limit.post_π

@[simp]
theorem limit.lift_post (c : Cone F) :
    G.map (limit.lift F c) ≫ limit.post F G = limit.lift (F ⋙ G) (G.mapCone c) := by
  ext
  rw [assoc, limit.post_π, ← G.map_comp, limit.lift_π, limit.lift_π]
  rfl
#align category_theory.limits.limit.lift_post CategoryTheory.Limits.limit.lift_post

@[simp]
theorem limit.post_post {E : Type u''} [Category.{v''} E] (H : D ⥤ E) [h : HasLimit ((F ⋙ G) ⋙ H)] :
    -- H G (limit F) ⟶ H (limit (F ⋙ G)) ⟶ limit ((F ⋙ G) ⋙ H) equals
    -- H G (limit F) ⟶ limit (F ⋙ (G ⋙ H))
    haveI : HasLimit (F ⋙  G ⋙  H) := h
    H.map (limit.post F G) ≫ limit.post (F ⋙ G) H = limit.post F (G ⋙ H) := by
  haveI : HasLimit (F ⋙  G ⋙  H) := h
  ext; erw [assoc, limit.post_π, ← H.map_comp, limit.post_π, limit.post_π]; rfl
#align category_theory.limits.limit.post_post CategoryTheory.Limits.limit.post_post

end Post

theorem limit.pre_post {D : Type u'} [Category.{v'} D] (E : K ⥤ J) (F : J ⥤ C) (G : C ⥤ D)
    [HasLimit F] [HasLimit (E ⋙ F)] [HasLimit (F ⋙ G)]
    [h : HasLimit ((E ⋙ F) ⋙ G)] :-- G (limit F) ⟶ G (limit (E ⋙ F)) ⟶ limit ((E ⋙ F) ⋙ G) vs
            -- G (limit F) ⟶ limit F ⋙ G ⟶ limit (E ⋙ (F ⋙ G)) or
    haveI : HasLimit (E ⋙  F ⋙  G) := h
    G.map (limit.pre F E) ≫ limit.post (E ⋙ F) G = limit.post F G ≫ limit.pre (F ⋙ G) E := by
  haveI : HasLimit (E ⋙  F ⋙  G) := h
  ext ; erw [assoc, limit.post_π, ← G.map_comp, limit.pre_π, assoc, limit.pre_π, limit.post_π]
#align category_theory.limits.limit.pre_post CategoryTheory.Limits.limit.pre_post

open CategoryTheory.Equivalence

instance hasLimitEquivalenceComp (e : K ≌ J) [HasLimit F] : HasLimit (e.functor ⋙ F) :=
  HasLimit.mk
    { cone := Cone.whisker e.functor (limit.cone F)
      isLimit := IsLimit.whiskerEquivalence (limit.isLimit F) e }
#align category_theory.limits.has_limit_equivalence_comp CategoryTheory.Limits.hasLimitEquivalenceComp

-- Porting note: testing whether this still needed
-- attribute [local elab_without_expected_type] inv_fun_id_assoc

-- not entirely sure why this is needed
/-- If a `E ⋙ F` has a limit, and `E` is an equivalence, we can construct a limit of `F`.
-/
theorem hasLimitOfEquivalenceComp (e : K ≌ J) [HasLimit (e.functor ⋙ F)] : HasLimit F := by
  haveI : HasLimit (e.inverse ⋙ e.functor ⋙ F) := Limits.hasLimitEquivalenceComp e.symm
  apply hasLimitOfIso (e.invFunIdAssoc F)
#align category_theory.limits.has_limit_of_equivalence_comp CategoryTheory.Limits.hasLimitOfEquivalenceComp

-- `hasLimitCompEquivalence` and `hasLimitOfCompEquivalence`
-- are proved in `CategoryTheory/Adjunction/Limits.lean`.
section LimFunctor

variable [HasLimitsOfShape J C]

section

/-- `limit F` is functorial in `F`, when `C` has all limits of shape `J`. -/
@[simps]
def lim : (J ⥤ C) ⥤ C where
  obj F := limit F
  map α := limMap α
  map_id F := by
    apply Limits.limit.hom_ext; intro j
    erw [limMap_π, Category.id_comp, Category.comp_id]
  map_comp α β := by
    apply Limits.limit.hom_ext; intro j
    erw [assoc, IsLimit.fac, IsLimit.fac, ← assoc, IsLimit.fac, assoc]; rfl
#align category_theory.limits.lim CategoryTheory.Limits.lim

end

variable {G : J ⥤ C} (α : F ⟶ G)

-- We generate this manually since `simps` gives it a weird name.
-- @[simp]
-- theorem limMap_eq_limMap : lim.map α = limMap α :=
--   rfl
-- #align category_theory.limits.lim_map_eq_lim_map CategoryTheory.Limits.limMap_eq_limMap

theorem limit.map_pre [HasLimitsOfShape K C] (E : K ⥤ J) :
    lim.map α ≫ limit.pre G E = limit.pre F E ≫ lim.map (whiskerLeft E α) := by
  ext
  simp
#align category_theory.limits.limit.map_pre CategoryTheory.Limits.limit.map_pre

theorem limit.map_pre' [HasLimitsOfShape K C] (F : J ⥤ C) {E₁ E₂ : K ⥤ J} (α : E₁ ⟶ E₂) :
    limit.pre F E₂ = limit.pre F E₁ ≫ lim.map (whiskerRight α F) := by
  ext1; simp [← category.assoc]
#align category_theory.limits.limit.map_pre' CategoryTheory.Limits.limit.map_pre'

theorem limit.id_pre (F : J ⥤ C) : limit.pre F (𝟭 _) = lim.map (Functor.leftUnitor F).inv := by
  aesop_cat
#align category_theory.limits.limit.id_pre CategoryTheory.Limits.limit.id_pre

theorem limit.map_post {D : Type u'} [Category.{v'} D] [HasLimitsOfShape J D] (H : C ⥤ D) :
    /- H (limit F) ⟶ H (limit G) ⟶ limit (G ⋙ H) vs
     H (limit F) ⟶ limit (F ⋙ H) ⟶ limit (G ⋙ H) -/
    H.map (limMap α) ≫ limit.post G H = limit.post F H ≫ limMap (whiskerRight α H) := by
  ext
  simp only [whiskerRight_app, limMap_π, assoc, limit.post_π_assoc, limit.post_π, ← H.map_comp]
#align category_theory.limits.limit.map_post CategoryTheory.Limits.limit.map_post

/-- The isomorphism between
morphisms from `W` to the cone point of the limit cone for `F`
and cones over `F` with cone point `W`
is natural in `F`.
-/
def limYoneda :
    lim ⋙ yoneda ⋙ (whiskeringRight _ _ _).obj uliftFunctor.{u₁} ≅ CategoryTheory.cones J C :=
  NatIso.ofComponents (fun F => NatIso.ofComponents (fun W => limit.homIso F (unop W))
    <| by intros ; funext ; aesop_cat) <| by intros ; ext ; funext ; aesop_cat
#align category_theory.limits.lim_yoneda CategoryTheory.Limits.limYoneda

/-- The constant functor and limit functor are adjoint to each other-/
def constLimAdj : (const J : C ⥤ J ⥤ C) ⊣ lim where
  homEquiv c g :=
    { toFun := fun f => limit.lift _ ⟨c, f⟩
      invFun := fun f =>
        { app := fun j => f ≫ limit.π _ _
          naturality := by aesop_cat }
      left_inv := fun _ => NatTrans.ext _ _ <| funext fun j => limit.lift_π _ _
      right_inv := fun α => limit.hom_ext fun j => limit.lift_π _ _ }
  unit :=
    { app := fun c => limit.lift _ ⟨_, 𝟙 _⟩
      naturality := fun _ _ _ => by aesop_cat }
  counit :=
    { app := fun g =>
        { app := limit.π _
          naturality := by aesop_cat }
      naturality := fun _ _ _ => by aesop_cat }
  homEquiv_unit := fun {c} {f} {g} => limit.hom_ext <| fun j => by simp
  homEquiv_counit {c} {g} {f} := NatTrans.ext _ _ <| funext fun j => rfl
#align category_theory.limits.const_lim_adj CategoryTheory.Limits.constLimAdj

instance : IsRightAdjoint (lim : (J ⥤ C) ⥤ C) :=
  ⟨_, constLimAdj⟩

end LimFunctor

instance limMap_mono' {F G : J ⥤ C} [HasLimitsOfShape J C] (α : F ⟶ G) [Mono α] : Mono (limMap α) :=
  (lim : (J ⥤ C) ⥤ C).map_mono α
#align category_theory.limits.lim_map_mono' CategoryTheory.Limits.limMap_mono'

instance limMap_mono {F G : J ⥤ C} [HasLimit F] [HasLimit G] (α : F ⟶ G) [∀ j, Mono (α.app j)] :
    Mono (limMap α) :=
  ⟨fun {Z} u v h =>
    limit.hom_ext fun j => (cancel_mono (α.app j)).1 <| by simpa using h =≫ limit.π _ j⟩
#align category_theory.limits.lim_map_mono CategoryTheory.Limits.limMap_mono

/-- We can transport limits of shape `J` along an equivalence `J ≌ J'`.
-/
theorem hasLimitsOfShape_of_equivalence {J' : Type u₂} [Category.{v₂} J'] (e : J ≌ J')
    [HasLimitsOfShape J C] : HasLimitsOfShape J' C := by
  constructor
  intro F
  apply hasLimitOfEquivalenceComp e
#align category_theory.limits.has_limits_of_shape_of_equivalence CategoryTheory.Limits.hasLimitsOfShape_of_equivalence

variable (C)

/-- `hasLimitsOfSizeShrink.{v u} C` tries to obtain `HasLimitsOfSize.{v u} C`
from some other `HasLimitsOfSize C`.
-/
theorem hasLimitsOfSizeShrink [HasLimitsOfSize.{max v₁ v₂, max u₁ u₂} C] :
    HasLimitsOfSize.{v₁, u₁} C :=
  ⟨fun J _ => hasLimitsOfShape_of_equivalence (ULiftHomULiftCategory.equiv.{v₂, u₂} J).symm⟩
#align category_theory.limits.has_limits_of_size_shrink CategoryTheory.Limits.hasLimitsOfSizeShrink

instance (priority := 100) hasSmallestLimitsOfHasLimits [HasLimits C] : HasLimitsOfSize.{0, 0} C :=
  hasLimitsOfSizeShrink.{0, 0} C
#align category_theory.limits.has_smallest_limits_of_has_limits CategoryTheory.Limits.hasSmallestLimitsOfHasLimits

end Limit

section Colimit

/-- `ColimitCocone F` contains a cocone over `F` together with the information that it is a
    colimit. -/
-- @[nolint has_nonempty_instance] -- Porting note: removed
structure ColimitCocone (F : J ⥤ C) where
  /-- The cocone itself -/
  cocone : Cocone F
  /-- The proof that it is the colimit cocone -/
  isColimit : IsColimit cocone
#align category_theory.limits.colimit_cocone CategoryTheory.Limits.ColimitCocone
#align category_theory.limits.colimit_cocone.is_colimit CategoryTheory.Limits.ColimitCocone.isColimit

/-- `HasColimit F` represents the mere existence of a colimit for `F`. -/
class HasColimit (F : J ⥤ C) : Prop where mk' ::
  /-- There exists a colimit for `F` -/
  exists_colimit : Nonempty (ColimitCocone F)
#align category_theory.limits.has_colimit CategoryTheory.Limits.HasColimit

theorem HasColimit.mk {F : J ⥤ C} (d : ColimitCocone F) : HasColimit F :=
  ⟨Nonempty.intro d⟩
#align category_theory.limits.has_colimit.mk CategoryTheory.Limits.HasColimit.mk

/-- Use the axiom of choice to extract explicit `ColimitCocone F` from `HasColimit F`. -/
def getColimitCocone (F : J ⥤ C) [HasColimit F] : ColimitCocone F :=
  Classical.choice <| HasColimit.exists_colimit
#align category_theory.limits.get_colimit_cocone CategoryTheory.Limits.getColimitCocone

variable (J C)

/-- `C` has colimits of shape `J` if there exists a colimit for every functor `F : J ⥤ C`. -/
class HasColimitsOfShape : Prop where
  /-- All `F : J ⥤ C` have colimits for a fixed `J` -/
  has_colimit : ∀ F : J ⥤ C, HasColimit F := by infer_instance
#align category_theory.limits.has_colimits_of_shape CategoryTheory.Limits.HasColimitsOfShape

/-- `C` has all colimits of size `v₁ u₁` (`HasColimitsOfSize.{v₁ u₁} C`)
if it has colimits of every shape `J : Type u₁` with `[Category.{v₁} J]`.
-/
class HasColimitsOfSize (C : Type u) [Category.{v} C] : Prop where
  /-- All `F : J ⥤ C` have colimits for all small `J` -/
  has_colimits_of_shape : ∀ (J : Type u₁) [Category.{v₁} J], HasColimitsOfShape J C := by
    infer_instance
#align category_theory.limits.has_colimits_of_size CategoryTheory.Limits.HasColimitsOfSize

/-- `C` has all (small) colimits if it has colimits of every shape that is as big as its hom-sets.
-/
abbrev HasColimits (C : Type u) [Category.{v} C] : Prop :=
  HasColimitsOfSize.{v, v} C
#align category_theory.limits.has_colimits CategoryTheory.Limits.HasColimits

theorem HasColimits.hasColimitsOfShape {C : Type u} [Category.{v} C] [HasColimits C] (J : Type v)
    [Category.{v} J] : HasColimitsOfShape J C :=
  HasColimitsOfSize.has_colimits_of_shape J
#align category_theory.limits.has_colimits.has_colimits_of_shape CategoryTheory.Limits.HasColimits.hasColimitsOfShape

variable {J C}

-- see Note [lower instance priority]
instance (priority := 100) hasColimitOfHasColimitsOfShape {J : Type u₁} [Category.{v₁} J]
    [HasColimitsOfShape J C] (F : J ⥤ C) : HasColimit F :=
  HasColimitsOfShape.has_colimit F
#align category_theory.limits.has_colimit_of_has_colimits_of_shape CategoryTheory.Limits.hasColimitOfHasColimitsOfShape

-- see Note [lower instance priority]
instance (priority := 100) hasColimitsOfShapeOfHasColimitsOfSize {J : Type u₁} [Category.{v₁} J]
    [HasColimitsOfSize.{v₁, u₁} C] : HasColimitsOfShape J C :=
  HasColimitsOfSize.has_colimits_of_shape J
#align category_theory.limits.has_colimits_of_shape_of_has_colimits_of_size CategoryTheory.Limits.hasColimitsOfShapeOfHasColimitsOfSize

-- Interface to the `HasColimit` class.
/-- An arbitrary choice of colimit cocone of a functor. -/
def colimit.cocone (F : J ⥤ C) [HasColimit F] : Cocone F :=
  (getColimitCocone F).cocone
#align category_theory.limits.colimit.cocone CategoryTheory.Limits.colimit.cocone

/-- An arbitrary choice of colimit object of a functor. -/
def colimit (F : J ⥤ C) [HasColimit F] :=
  (colimit.cocone F).pt
#align category_theory.limits.colimit CategoryTheory.Limits.colimit

/-- The coprojection from a value of the functor to the colimit object. -/
def colimit.ι (F : J ⥤ C) [HasColimit F] (j : J) : F.obj j ⟶ colimit F :=
  (colimit.cocone F).ι.app j
#align category_theory.limits.colimit.ι CategoryTheory.Limits.colimit.ι

@[simp]
theorem colimit.cocone_ι {F : J ⥤ C} [HasColimit F] (j : J) :
    (colimit.cocone F).ι.app j = colimit.ι _ j :=
  rfl
#align category_theory.limits.colimit.cocone_ι CategoryTheory.Limits.colimit.cocone_ι

@[simp]
theorem colimit.cocone_x {F : J ⥤ C} [HasColimit F] : (colimit.cocone F).pt = colimit F :=
  rfl
set_option linter.uppercaseLean3 false in
#align category_theory.limits.colimit.cocone_X CategoryTheory.Limits.colimit.cocone_x

@[reassoc (attr := simp)]
theorem colimit.w (F : J ⥤ C) [HasColimit F] {j j' : J} (f : j ⟶ j') :
    F.map f ≫ colimit.ι F j' = colimit.ι F j :=
  (colimit.cocone F).w f
#align category_theory.limits.colimit.w CategoryTheory.Limits.colimit.w

/-- Evidence that the arbitrary choice of cocone is a colimit cocone. -/
def colimit.isColimit (F : J ⥤ C) [HasColimit F] : IsColimit (colimit.cocone F) :=
  (getColimitCocone F).isColimit
#align category_theory.limits.colimit.is_colimit CategoryTheory.Limits.colimit.isColimit

/-- The morphism from the colimit object to the cone point of any other cocone. -/
def colimit.desc (F : J ⥤ C) [HasColimit F] (c : Cocone F) : colimit F ⟶ c.pt :=
  (colimit.isColimit F).desc c
#align category_theory.limits.colimit.desc CategoryTheory.Limits.colimit.desc

@[simp]
theorem colimit.isColimit_desc {F : J ⥤ C} [HasColimit F] (c : Cocone F) :
    (colimit.isColimit F).desc c = colimit.desc F c :=
  rfl
#align category_theory.limits.colimit.is_colimit_desc CategoryTheory.Limits.colimit.isColimit_desc

/-- We have lots of lemmas describing how to simplify `colimit.ι F j ≫ _`,
and combined with `colimit.ext` we rely on these lemmas for many calculations.

However, since `category.assoc` is a `@[simp]` lemma, often expressions are
right associated, and it's hard to apply these lemmas about `colimit.ι`.

We thus use `reassoc` to define additional `@[simp]` lemmas, with an arbitrary extra morphism.
(see `Tactic/reassoc_axiom.lean`)
 -/
@[reassoc (attr := simp)]
theorem colimit.ι_desc {F : J ⥤ C} [HasColimit F] (c : Cocone F) (j : J) :
    colimit.ι F j ≫ colimit.desc F c = c.ι.app j :=
  IsColimit.fac _ c j
#align category_theory.limits.colimit.ι_desc CategoryTheory.Limits.colimit.ι_desc

/-- Functoriality of colimits.

Usually this morphism should be accessed through `colim.map`,
but may be needed separately when you have specified colimits for the source and target functors,
but not necessarily for all functors of shape `J`.
-/
def colimMap {F G : J ⥤ C} [HasColimit F] [HasColimit G] (α : F ⟶ G) : colimit F ⟶ colimit G :=
  IsColimit.map (colimit.isColimit F) _ α
#align category_theory.limits.colim_map CategoryTheory.Limits.colimMap

@[reassoc (attr := simp)]
theorem ι_colimMap {F G : J ⥤ C} [HasColimit F] [HasColimit G] (α : F ⟶ G) (j : J) :
    colimit.ι F j ≫ colimMap α = α.app j ≫ colimit.ι G j :=
  colimit.ι_desc _ j
#align category_theory.limits.ι_colim_map CategoryTheory.Limits.ι_colimMap

/-- The cocone morphism from the arbitrary choice of colimit cocone to any cocone. -/
def colimit.coconeMorphism {F : J ⥤ C} [HasColimit F] (c : Cocone F) : colimit.cocone F ⟶ c :=
  (colimit.isColimit F).descCoconeMorphism c
#align category_theory.limits.colimit.cocone_morphism CategoryTheory.Limits.colimit.coconeMorphism

@[simp]
theorem colimit.coconeMorphism_hom {F : J ⥤ C} [HasColimit F] (c : Cocone F) :
    (colimit.coconeMorphism c).Hom = colimit.desc F c :=
  rfl
#align category_theory.limits.colimit.cocone_morphism_hom CategoryTheory.Limits.colimit.coconeMorphism_hom

theorem colimit.ι_coconeMorphism {F : J ⥤ C} [HasColimit F] (c : Cocone F) (j : J) :
    colimit.ι F j ≫ (colimit.coconeMorphism c).Hom = c.ι.app j := by simp
#align category_theory.limits.colimit.ι_cocone_morphism CategoryTheory.Limits.colimit.ι_coconeMorphism

@[reassoc (attr := simp)]
theorem colimit.comp_coconePointUniqueUpToIso_hom {F : J ⥤ C} [HasColimit F] {c : Cocone F}
    (hc : IsColimit c) (j : J) :
    colimit.ι F j ≫ (IsColimit.coconePointUniqueUpToIso (colimit.isColimit _) hc).hom = c.ι.app j :=
  IsColimit.comp_coconePointUniqueUpToIso_hom _ _ _
#align category_theory.limits.colimit.comp_cocone_point_unique_up_to_iso_hom CategoryTheory.Limits.colimit.comp_coconePointUniqueUpToIso_hom

@[reassoc (attr := simp)]
theorem colimit.comp_coconePointUniqueUpToIso_inv {F : J ⥤ C} [HasColimit F] {c : Cocone F}
    (hc : IsColimit c) (j : J) :
    colimit.ι F j ≫ (IsColimit.coconePointUniqueUpToIso hc (colimit.isColimit _)).inv = c.ι.app j :=
  IsColimit.comp_coconePointUniqueUpToIso_inv _ _ _
#align category_theory.limits.colimit.comp_cocone_point_unique_up_to_iso_inv CategoryTheory.Limits.colimit.comp_coconePointUniqueUpToIso_inv

theorem colimit.existsUnique {F : J ⥤ C} [HasColimit F] (t : Cocone F) :
    ∃! d : colimit F ⟶ t.pt, ∀ j, colimit.ι F j ≫ d = t.ι.app j :=
  (colimit.isColimit F).existsUnique _
#align category_theory.limits.colimit.exists_unique CategoryTheory.Limits.colimit.existsUnique

/--
Given any other colimit cocone for `F`, the chosen `colimit F` is isomorphic to the cocone point.
-/
def colimit.isoColimitCocone {F : J ⥤ C} [HasColimit F] (t : ColimitCocone F) :
    colimit F ≅ t.cocone.pt :=
  IsColimit.coconePointUniqueUpToIso (colimit.isColimit F) t.isColimit
#align category_theory.limits.colimit.iso_colimit_cocone CategoryTheory.Limits.colimit.isoColimitCocone

@[reassoc (attr := simp)]
theorem colimit.isoColimitCocone_ι_hom {F : J ⥤ C} [HasColimit F] (t : ColimitCocone F) (j : J) :
    colimit.ι F j ≫ (colimit.isoColimitCocone t).hom = t.cocone.ι.app j := by
  dsimp [colimit.isoColimitCocone, IsColimit.coconePointUniqueUpToIso]
  aesop_cat
#align category_theory.limits.colimit.iso_colimit_cocone_ι_hom CategoryTheory.Limits.colimit.isoColimitCocone_ι_hom

@[reassoc (attr := simp)]
theorem colimit.isoColimitCocone_ι_inv {F : J ⥤ C} [HasColimit F] (t : ColimitCocone F) (j : J) :
    t.cocone.ι.app j ≫ (colimit.isoColimitCocone t).inv = colimit.ι F j := by
  dsimp [colimit.isoColimitCocone, IsColimit.coconePointUniqueUpToIso]
  aesop_cat
#align category_theory.limits.colimit.iso_colimit_cocone_ι_inv CategoryTheory.Limits.colimit.isoColimitCocone_ι_inv

@[ext]
theorem colimit.hom_ext {F : J ⥤ C} [HasColimit F] {X : C} {f f' : colimit F ⟶ X}
    (w : ∀ j, colimit.ι F j ≫ f = colimit.ι F j ≫ f') : f = f' :=
  (colimit.isColimit F).hom_ext w
#align category_theory.limits.colimit.hom_ext CategoryTheory.Limits.colimit.hom_ext

@[simp]
theorem colimit.desc_cocone {F : J ⥤ C} [HasColimit F] :
    colimit.desc F (colimit.cocone F) = 𝟙 (colimit F) :=
  (colimit.isColimit _).desc_self
#align category_theory.limits.colimit.desc_cocone CategoryTheory.Limits.colimit.desc_cocone

/-- The isomorphism (in `Type`) between
morphisms from the colimit object to a specified object `W`,
and cocones with cone point `W`.
-/
def colimit.homIso (F : J ⥤ C) [HasColimit F] (W : C) :
    ULift.{u₁} (colimit F ⟶ W : Type v) ≅ F.cocones.obj W :=
  (colimit.isColimit F).homIso W
#align category_theory.limits.colimit.hom_iso CategoryTheory.Limits.colimit.homIso

@[simp]
theorem colimit.homIso_hom (F : J ⥤ C) [HasColimit F] {W : C} (f : ULift (colimit F ⟶ W)) :
    (colimit.homIso F W).hom f = (colimit.cocone F).ι ≫ (const J).map f.down :=
  (colimit.isColimit F).homIso_hom f
#align category_theory.limits.colimit.hom_iso_hom CategoryTheory.Limits.colimit.homIso_hom

/-- The isomorphism (in `Type`) between
morphisms from the colimit object to a specified object `W`,
and an explicit componentwise description of cocones with cone point `W`.
-/
def colimit.homIso' (F : J ⥤ C) [HasColimit F] (W : C) :
    ULift.{u₁} (colimit F ⟶ W : Type v) ≅
      { p : ∀ j, F.obj j ⟶ W // ∀ {j j'} (f : j ⟶ j'), F.map f ≫ p j' = p j } :=
  (colimit.isColimit F).homIso' W
#align category_theory.limits.colimit.hom_iso' CategoryTheory.Limits.colimit.homIso'

theorem colimit.desc_extend (F : J ⥤ C) [HasColimit F] (c : Cocone F) {X : C} (f : c.pt ⟶ X) :
    colimit.desc F (c.extend f) = colimit.desc F c ≫ f := by ext1; rw [← Category.assoc]; simp
#align category_theory.limits.colimit.desc_extend CategoryTheory.Limits.colimit.desc_extend

-- This has the isomorphism pointing in the opposite direction than in `has_limit_of_iso`.
-- This is intentional; it seems to help with elaboration.
/-- If `F` has a colimit, so does any naturally isomorphic functor.
-/
theorem hasColimitOfIso {F G : J ⥤ C} [HasColimit F] (α : G ≅ F) : HasColimit G :=
  HasColimit.mk
    { cocone := (Cocones.precompose α.hom).obj (colimit.cocone F)
      isColimit :=
        { desc := fun s => colimit.desc F ((Cocones.precompose α.inv).obj s)
          fac := fun s j =>
            by
            rw [Cocones.precompose_obj_ι, NatTrans.comp_app, colimit.cocone_ι]
            rw [Category.assoc, colimit.ι_desc, ← NatIso.app_hom, ← Iso.eq_inv_comp]; rfl
          uniq := fun s m w => by
            apply colimit.hom_ext; intro j
            rw [colimit.ι_desc, Cocones.precompose_obj_ι, NatTrans.comp_app, ← NatIso.app_inv,
              Iso.eq_inv_comp]
            simpa using w j } }
#align category_theory.limits.has_colimit_of_iso CategoryTheory.Limits.hasColimitOfIso

/-- If a functor `G` has the same collection of cocones as a functor `F`
which has a colimit, then `G` also has a colimit. -/
theorem HasColimit.ofCoconesIso {K : Type u₁} [Category.{v₂} K] (F : J ⥤ C) (G : K ⥤ C)
    (h : F.cocones ≅ G.cocones) [HasColimit F] : HasColimit G :=
  HasColimit.mk ⟨_, IsColimit.ofNatIso (IsColimit.natIso (colimit.isColimit F) ≪≫ h)⟩
#align category_theory.limits.has_colimit.of_cocones_iso CategoryTheory.Limits.HasColimit.ofCoconesIso

/-- The colimits of `F : J ⥤ C` and `G : J ⥤ C` are isomorphic,
if the functors are naturally isomorphic.
-/
def HasColimit.isoOfNatIso {F G : J ⥤ C} [HasColimit F] [HasColimit G] (w : F ≅ G) :
    colimit F ≅ colimit G :=
  IsColimit.coconePointsIsoOfNatIso (colimit.isColimit F) (colimit.isColimit G) w
#align category_theory.limits.has_colimit.iso_of_nat_iso CategoryTheory.Limits.HasColimit.isoOfNatIso

@[reassoc (attr := simp)]
theorem HasColimit.isoOfNatIso_ι_hom {F G : J ⥤ C} [HasColimit F] [HasColimit G] (w : F ≅ G)
    (j : J) : colimit.ι F j ≫ (HasColimit.isoOfNatIso w).hom = w.hom.app j ≫ colimit.ι G j :=
  IsColimit.comp_coconePointsIsoOfNatIso_hom _ _ _ _
#align category_theory.limits.has_colimit.iso_of_nat_iso_ι_hom CategoryTheory.Limits.HasColimit.isoOfNatIso_ι_hom

@[reassoc (attr := simp)]
theorem HasColimit.isoOfNatIso_ι_inv {F G : J ⥤ C} [HasColimit F] [HasColimit G] (w : F ≅ G)
    (j : J) : colimit.ι G j ≫ (HasColimit.isoOfNatIso w).inv = w.inv.app j ≫ colimit.ι F j :=
  IsColimit.comp_coconePointsIsoOfNatIso_inv _ _ _ _
#align category_theory.limits.has_colimit.iso_of_nat_iso_ι_inv CategoryTheory.Limits.HasColimit.isoOfNatIso_ι_inv

@[reassoc (attr := simp)]
theorem HasColimit.isoOfNatIso_hom_desc {F G : J ⥤ C} [HasColimit F] [HasColimit G] (t : Cocone G)
    (w : F ≅ G) :
    (HasColimit.isoOfNatIso w).hom ≫ colimit.desc G t =
      colimit.desc F ((Cocones.precompose w.hom).obj _) :=
  IsColimit.coconePointsIsoOfNatIso_hom_desc _ _ _
#align category_theory.limits.has_colimit.iso_of_nat_iso_hom_desc CategoryTheory.Limits.HasColimit.isoOfNatIso_hom_desc

@[reassoc (attr := simp)]
theorem HasColimit.isoOfNatIso_inv_desc {F G : J ⥤ C} [HasColimit F] [HasColimit G] (t : Cocone F)
    (w : F ≅ G) :
    (HasColimit.isoOfNatIso w).inv ≫ colimit.desc F t =
      colimit.desc G ((Cocones.precompose w.inv).obj _) :=
  IsColimit.coconePointsIsoOfNatIso_inv_desc _ _ _
#align category_theory.limits.has_colimit.iso_of_nat_iso_inv_desc CategoryTheory.Limits.HasColimit.isoOfNatIso_inv_desc

/-- The colimits of `F : J ⥤ C` and `G : K ⥤ C` are isomorphic,
if there is an equivalence `e : J ≌ K` making the triangle commute up to natural isomorphism.
-/
def HasColimit.isoOfEquivalence {F : J ⥤ C} [HasColimit F] {G : K ⥤ C} [HasColimit G] (e : J ≌ K)
    (w : e.functor ⋙ G ≅ F) : colimit F ≅ colimit G :=
  IsColimit.coconePointsIsoOfEquivalence (colimit.isColimit F) (colimit.isColimit G) e w
#align category_theory.limits.has_colimit.iso_of_equivalence CategoryTheory.Limits.HasColimit.isoOfEquivalence

@[simp]
theorem HasColimit.isoOfEquivalence_hom_π {F : J ⥤ C} [HasColimit F] {G : K ⥤ C} [HasColimit G]
    (e : J ≌ K) (w : e.functor ⋙ G ≅ F) (j : J) :
    colimit.ι F j ≫ (HasColimit.isoOfEquivalence e w).hom =
      F.map (e.unit.app j) ≫ w.inv.app _ ≫ colimit.ι G _ := by
  simp [HasColimit.isoOfEquivalence, IsColimit.coconePointsIsoOfEquivalence_inv]
#align category_theory.limits.has_colimit.iso_of_equivalence_hom_π CategoryTheory.Limits.HasColimit.isoOfEquivalence_hom_π

@[simp]
theorem HasColimit.isoOfEquivalence_inv_π {F : J ⥤ C} [HasColimit F] {G : K ⥤ C} [HasColimit G]
    (e : J ≌ K) (w : e.functor ⋙ G ≅ F) (k : K) :
    colimit.ι G k ≫ (HasColimit.isoOfEquivalence e w).inv =
      G.map (e.counitInv.app k) ≫ w.hom.app (e.inverse.obj k) ≫ colimit.ι F (e.inverse.obj k) := by
  simp [HasColimit.isoOfEquivalence, IsColimit.coconePointsIsoOfEquivalence_inv]
#align category_theory.limits.has_colimit.iso_of_equivalence_inv_π CategoryTheory.Limits.HasColimit.isoOfEquivalence_inv_π

section Pre

variable (F) [HasColimit F] (E : K ⥤ J) [HasColimit (E ⋙ F)]

/-- The canonical morphism from the colimit of `E ⋙ F` to the colimit of `F`.
-/
def colimit.pre : colimit (E ⋙ F) ⟶ colimit F :=
  colimit.desc (E ⋙ F) ((colimit.cocone F).whisker E)
#align category_theory.limits.colimit.pre CategoryTheory.Limits.colimit.pre

@[reassoc (attr := simp)]
theorem colimit.ι_pre (k : K) : colimit.ι (E ⋙ F) k ≫ colimit.pre F E = colimit.ι F (E.obj k) := by
  erw [IsColimit.fac]
  rfl
#align category_theory.limits.colimit.ι_pre CategoryTheory.Limits.colimit.ι_pre

@[reassoc (attr := simp)]
theorem colimit.pre_desc (c : Cocone F) :
    colimit.pre F E ≫ colimit.desc F c = colimit.desc (E ⋙ F) (c.whisker E) := by
  ext; rw [← assoc, colimit.ι_pre]; simp
#align category_theory.limits.colimit.pre_desc CategoryTheory.Limits.colimit.pre_desc

variable {L : Type u₃} [Category.{v₃} L]

variable (D : L ⥤ K) [HasColimit (D ⋙ E ⋙ F)]

@[simp]
theorem colimit.pre_pre [h : HasColimit (D ⋙  E ⋙  F)] :
    haveI : HasColimit ((D ⋙  E) ⋙  F) := h
    colimit.pre (E ⋙ F) D ≫ colimit.pre F E = colimit.pre F (D ⋙ E) := by
  ext j
  rw [← assoc, colimit.ι_pre, colimit.ι_pre]
  haveI : HasColimit ((D ⋙ E) ⋙ F) := h
  exact (colimit.ι_pre F (D ⋙ E) j).symm
#align category_theory.limits.colimit.pre_pre CategoryTheory.Limits.colimit.pre_pre

variable {E F}

/-- -
If we have particular colimit cocones available for `E ⋙ F` and for `F`,
we obtain a formula for `colimit.pre F E`.
-/
theorem colimit.pre_eq (s : ColimitCocone (E ⋙ F)) (t : ColimitCocone F) :
    colimit.pre F E =
      (colimit.isoColimitCocone s).hom ≫
        s.isColimit.desc (t.cocone.whisker E) ≫ (colimit.isoColimitCocone t).inv :=
  by aesop_cat
#align category_theory.limits.colimit.pre_eq CategoryTheory.Limits.colimit.pre_eq

end Pre

section Post

variable {D : Type u'} [Category.{v'} D]

variable (F) [HasColimit F] (G : C ⥤ D) [HasColimit (F ⋙ G)]

/-- The canonical morphism from `G` applied to the colimit of `F ⋙ G`
to `G` applied to the colimit of `F`.
-/
def colimit.post : colimit (F ⋙ G) ⟶ G.obj (colimit F) :=
  colimit.desc (F ⋙ G) (G.mapCocone (colimit.cocone F))
#align category_theory.limits.colimit.post CategoryTheory.Limits.colimit.post

@[reassoc (attr := simp)]
theorem colimit.ι_post (j : J) : colimit.ι (F ⋙ G) j ≫ colimit.post F G = G.map (colimit.ι F j) :=
  by
  erw [IsColimit.fac]
  rfl
#align category_theory.limits.colimit.ι_post CategoryTheory.Limits.colimit.ι_post

@[simp]
theorem colimit.post_desc (c : Cocone F) :
    colimit.post F G ≫ G.map (colimit.desc F c) = colimit.desc (F ⋙ G) (G.mapCocone c) := by
  ext
  rw [← assoc, colimit.ι_post, ← G.map_comp, colimit.ι_desc, colimit.ι_desc]
  rfl
#align category_theory.limits.colimit.post_desc CategoryTheory.Limits.colimit.post_desc

@[simp]
theorem colimit.post_post {E : Type u''} [Category.{v''} E] (H : D ⥤ E)
    -- H G (colimit F) ⟶ H (colimit (F ⋙ G)) ⟶ colimit ((F ⋙ G) ⋙ H) equals
    -- H G (colimit F) ⟶ colimit (F ⋙ (G ⋙ H))
    [h : HasColimit ((F ⋙ G) ⋙ H)] : haveI : HasColimit (F ⋙  G ⋙  H) := h
    colimit.post (F ⋙ G) H ≫ H.map (colimit.post F G) = colimit.post F (G ⋙ H) := by
  ext j
  rw [← assoc, colimit.ι_post, ← H.map_comp, colimit.ι_post]
  haveI : HasColimit (F ⋙  G ⋙  H) := h
  exact (colimit.ι_post F (G ⋙ H) j).symm
#align category_theory.limits.colimit.post_post CategoryTheory.Limits.colimit.post_post

end Post

theorem colimit.pre_post {D : Type u'} [Category.{v'} D] (E : K ⥤ J) (F : J ⥤ C) (G : C ⥤ D)
    [HasColimit F] [HasColimit (E ⋙ F)] [HasColimit (F ⋙ G)] [h : HasColimit ((E ⋙ F) ⋙ G)] :
    -- G (colimit F) ⟶ G (colimit (E ⋙ F)) ⟶ colimit ((E ⋙ F) ⋙ G) vs
    -- G (colimit F) ⟶ colimit F ⋙ G ⟶ colimit (E ⋙ (F ⋙ G)) or
    haveI : HasColimit (E ⋙  F ⋙  G) := h
    colimit.post (E ⋙ F) G ≫ G.map (colimit.pre F E) =
      colimit.pre (F ⋙ G) E ≫ colimit.post F G := by
  ext j
  rw [← assoc, colimit.ι_post, ← G.map_comp, colimit.ι_pre, ← assoc]
  haveI : HasColimit (E ⋙ F ⋙ G) := h
  erw [colimit.ι_pre (F ⋙ G) E j, colimit.ι_post]
#align category_theory.limits.colimit.pre_post CategoryTheory.Limits.colimit.pre_post

open CategoryTheory.Equivalence

instance hasColimit_equivalence_comp (e : K ≌ J) [HasColimit F] : HasColimit (e.functor ⋙ F) :=
  HasColimit.mk
    { cocone := Cocone.whisker e.functor (colimit.cocone F)
      isColimit := IsColimit.whiskerEquivalence (colimit.isColimit F) e }
#align category_theory.limits.has_colimit_equivalence_comp CategoryTheory.Limits.hasColimit_equivalence_comp

/-- If a `E ⋙ F` has a colimit, and `E` is an equivalence, we can construct a colimit of `F`.
-/
theorem hasColimit_of_equivalence_comp (e : K ≌ J) [HasColimit (e.functor ⋙ F)] : HasColimit F := by
  haveI : HasColimit (e.inverse ⋙ e.functor ⋙ F) := Limits.hasColimit_equivalence_comp e.symm
  apply hasColimitOfIso (e.invFunIdAssoc F).symm
#align category_theory.limits.has_colimit_of_equivalence_comp CategoryTheory.Limits.hasColimit_of_equivalence_comp

section ColimFunctor

variable [HasColimitsOfShape J C]

section

-- attribute [local simp] colimMap -- Porting note: errors out colim.map_id and map_comp now

/-- `colimit F` is functorial in `F`, when `C` has all colimits of shape `J`. -/
@[simps] -- Porting note: simps on all fields now
def colim : (J ⥤ C) ⥤ C where
  obj F := colimit F
  map α := colimMap α
  map_id F := by aesop_cat
  map_comp α β := by aesop_cat
#align category_theory.limits.colim CategoryTheory.Limits.colim

end

variable {G : J ⥤ C} (α : F ⟶ G)

-- @[reassoc (attr := simp)] Porting note: now simp can prove these
@[reassoc]
theorem colimit.ι_map (j : J) : colimit.ι F j ≫ colim.map α = α.app j ≫ colimit.ι G j := by simp
#align category_theory.limits.colimit.ι_map CategoryTheory.Limits.colimit.ι_map

@[simp] -- Porting note: proof adjusted to account for @[simps] on all fields of colim
theorem colimit.map_desc (c : Cocone G) :
    colimMap α ≫ colimit.desc G c = colimit.desc F ((Cocones.precompose α).obj c) := by
  apply Limits.colimit.hom_ext; intro j
  simp [← assoc, colimit.ι_map, assoc, colimit.ι_desc, colimit.ι_desc]
#align category_theory.limits.colimit.map_desc CategoryTheory.Limits.colimit.map_desc

theorem colimit.pre_map [HasColimitsOfShape K C] (E : K ⥤ J) :
    colimit.pre F E ≫ colim.map α = colim.map (whiskerLeft E α) ≫ colimit.pre G E := by
  ext
  rw [← assoc, colimit.ι_pre, colimit.ι_map, ← assoc, colimit.ι_map, assoc, colimit.ι_pre]
  rfl
#align category_theory.limits.colimit.pre_map CategoryTheory.Limits.colimit.pre_map

theorem colimit.pre_map' [HasColimitsOfShape K C] (F : J ⥤ C) {E₁ E₂ : K ⥤ J} (α : E₁ ⟶ E₂) :
    colimit.pre F E₁ = colim.map (whiskerRight α F) ≫ colimit.pre F E₂ := by
  ext1; simp [← Category.assoc]
#align category_theory.limits.colimit.pre_map' CategoryTheory.Limits.colimit.pre_map'

theorem colimit.pre_id (F : J ⥤ C) : colimit.pre F (𝟭 _) = colim.map (Functor.leftUnitor F).hom :=
  by aesop_cat
#align category_theory.limits.colimit.pre_id CategoryTheory.Limits.colimit.pre_id

theorem colimit.map_post {D : Type u'} [Category.{v'} D] [HasColimitsOfShape J D]
    (H : C ⥤ D) :/- H (colimit F) ⟶ H (colimit G) ⟶ colimit (G ⋙ H) vs
             H (colimit F) ⟶ colimit (F ⋙ H) ⟶ colimit (G ⋙ H) -/
          colimit.post
          F H ≫
        H.map (colim.map α) =
      colim.map (whiskerRight α H) ≫ colimit.post G H := by
  ext
  rw [← assoc, colimit.ι_post, ← H.map_comp, colimit.ι_map, H.map_comp]
  rw [← assoc, colimit.ι_map, assoc, colimit.ι_post]
  rfl
#align category_theory.limits.colimit.map_post CategoryTheory.Limits.colimit.map_post

/-- The isomorphism between
morphisms from the cone point of the colimit cocone for `F` to `W`
and cocones over `F` with cone point `W`
is natural in `F`.
-/
def colimCoyoneda : colim.op ⋙ coyoneda ⋙ (whiskeringRight _ _ _).obj uliftFunctor.{u₁}
    ≅ CategoryTheory.cocones J C :=
  NatIso.ofComponents (fun F => NatIso.ofComponents (fun W => colimit.homIso (unop F) (op W))
    <| by intros ; funext ; aesop_cat) <| by intros ; ext ; funext ; aesop_cat
#align category_theory.limits.colim_coyoneda CategoryTheory.Limits.colimCoyoneda

/-- The colimit functor and constant functor are adjoint to each other
-/
def colimConstAdj : (colim : (J ⥤ C) ⥤ C) ⊣ const J where
  homEquiv f c :=
    { toFun := fun g =>
        { app := fun _ => colimit.ι _ _ ≫ g
          naturality := by aesop_cat }
      invFun := fun g => colimit.desc _ ⟨_, g⟩
      left_inv := fun _ => colimit.hom_ext fun j => colimit.ι_desc _ _
      right_inv := fun _ => NatTrans.ext _ _ <| funext fun j => colimit.ι_desc _ _ }
  unit :=
    { app := fun g =>
        { app := colimit.ι _
          naturality := by aesop_cat }
      naturality := by aesop_cat }
  counit :=
    { app := fun c => colimit.desc _ ⟨_, 𝟙 _⟩
      naturality := by aesop_cat }
  homEquiv_unit := fun {c} {f} {g} => NatTrans.ext _ _ <| funext fun _ => rfl
  homEquiv_counit {c} {f} {g} := colimit.hom_ext <| fun _ => by simp
#align category_theory.limits.colim_const_adj CategoryTheory.Limits.colimConstAdj

instance : IsLeftAdjoint (colim : (J ⥤ C) ⥤ C) :=
  ⟨_, colimConstAdj⟩

end ColimFunctor

instance colimMap_epi' {F G : J ⥤ C} [HasColimitsOfShape J C] (α : F ⟶ G) [Epi α] :
    Epi (colimMap α) :=
  (colim : (J ⥤ C) ⥤ C).map_epi α
#align category_theory.limits.colim_map_epi' CategoryTheory.Limits.colimMap_epi'

instance colimMap_epi {F G : J ⥤ C} [HasColimit F] [HasColimit G] (α : F ⟶ G) [∀ j, Epi (α.app j)] :
    Epi (colimMap α) :=
  ⟨fun {Z} u v h =>
    colimit.hom_ext fun j => (cancel_epi (α.app j)).1 <| by simpa using colimit.ι _ j ≫= h⟩
#align category_theory.limits.colim_map_epi CategoryTheory.Limits.colimMap_epi

/-- We can transport colimits of shape `J` along an equivalence `J ≌ J'`.
-/
theorem hasColimitsOfShape_of_equivalence {J' : Type u₂} [Category.{v₂} J'] (e : J ≌ J')
    [HasColimitsOfShape J C] : HasColimitsOfShape J' C := by
  constructor
  intro F
  apply hasColimit_of_equivalence_comp e
#align category_theory.limits.has_colimits_of_shape_of_equivalence CategoryTheory.Limits.hasColimitsOfShape_of_equivalence

variable (C)

/-- `hasColimitsOfSizeShrink.{v u} C` tries to obtain `HasColimitsOfSize.{v u} C`
from some other `HasColimitsOfSize C`.
-/
theorem hasColimitsOfSize_shrink [HasColimitsOfSize.{max v₁ v₂, max u₁ u₂} C] :
    HasColimitsOfSize.{v₁, u₁} C :=
  ⟨fun J _ => hasColimitsOfShape_of_equivalence (ULiftHomULiftCategory.equiv.{v₂, u₂} J).symm⟩
#align category_theory.limits.has_colimits_of_size_shrink CategoryTheory.Limits.hasColimitsOfSize_shrink

instance (priority := 100) hasSmallestColimitsOfHasColimits [HasColimits C] :
    HasColimitsOfSize.{0, 0} C :=
  hasColimitsOfSize_shrink.{0, 0} C
#align category_theory.limits.has_smallest_colimits_of_has_colimits CategoryTheory.Limits.hasSmallestColimitsOfHasColimits

end Colimit

section Opposite

/-- If `t : Cone F` is a limit cone, then `t.op : Cocone F.op` is a colimit cocone.
-/
def IsLimit.op {t : Cone F} (P : IsLimit t) : IsColimit t.op where
  desc s := (P.lift s.unop).op
  fac s j := congrArg Quiver.Hom.op (P.fac s.unop (unop j))
  uniq s m w := by
    dsimp
    rw [← P.uniq s.unop m.unop]
    · rfl
    · dsimp
      intro j
      rw [← w]
      rfl
#align category_theory.limits.is_limit.op CategoryTheory.Limits.IsLimit.op

/-- If `t : Cocone F` is a colimit cocone, then `t.op : Cone F.op` is a limit cone.
-/
def IsColimit.op {t : Cocone F} (P : IsColimit t) : IsLimit t.op where
  lift s := (P.desc s.unop).op
  fac s j := congrArg Quiver.Hom.op (P.fac s.unop (unop j))
  uniq s m w := by
    dsimp
    rw [← P.uniq s.unop m.unop]
    · rfl
    · dsimp
      intro j
      rw [← w]
      rfl
#align category_theory.limits.is_colimit.op CategoryTheory.Limits.IsColimit.op

/-- If `t : Cone F.op` is a limit cone, then `t.unop : Cocone F` is a colimit cocone.
-/
def IsLimit.unop {t : Cone F.op} (P : IsLimit t) : IsColimit t.unop where
  desc s := (P.lift s.op).unop
  fac s j := congrArg Quiver.Hom.unop (P.fac s.op (Opposite.op j))
  -- Porting note: thinks `op j` is `IsLimit.op j`
  uniq s m w := by
    dsimp
    rw [← P.uniq s.op m.op]
    · rfl
    · dsimp
      intro j
      rw [← w]
      rfl
#align category_theory.limits.is_limit.unop CategoryTheory.Limits.IsLimit.unop

/-- If `t : Cocone F.op` is a colimit cocone, then `t.unop : Cone F.` is a limit cone.
-/
def IsColimit.unop {t : Cocone F.op} (P : IsColimit t) : IsLimit t.unop where
  lift s := (P.desc s.op).unop
  fac s j := congrArg Quiver.Hom.unop (P.fac s.op (Opposite.op j))
  -- Porting note: thinks `op j` is `IsLimit.op j`
  uniq s m w := by
    dsimp
    rw [← P.uniq s.op m.op]
    · rfl
    · dsimp
      intro j
      rw [← w]
      rfl
#align category_theory.limits.is_colimit.unop CategoryTheory.Limits.IsColimit.unop

/-- `t : Cone F` is a limit cone if and only is `t.op : Cocone F.op` is a colimit cocone.
-/
def isLimitEquivIsColimitOp {t : Cone F} : IsLimit t ≃ IsColimit t.op :=
  equivOfSubsingletonOfSubsingleton IsLimit.op fun P =>
    P.unop.ofIsoLimit (Cones.ext (Iso.refl _) (by aesop_cat))
#align category_theory.limits.is_limit_equiv_is_colimit_op CategoryTheory.Limits.isLimitEquivIsColimitOp

/-- `t : Cocone F` is a colimit cocone if and only is `t.op : Cone F.op` is a limit cone.
-/
def isColimitEquivIsLimitOp {t : Cocone F} : IsColimit t ≃ IsLimit t.op :=
  equivOfSubsingletonOfSubsingleton IsColimit.op fun P =>
    P.unop.ofIsoColimit (Cocones.ext (Iso.refl _) (by aesop_cat))
#align category_theory.limits.is_colimit_equiv_is_limit_op CategoryTheory.Limits.isColimitEquivIsLimitOp

end Opposite


import .topological_space
import .continuity

namespace hidden

namespace topological_space

variables {α β γ: Type}

open classical

local attribute [instance] classical.prop_decidable

def is_open_cover (X: topological_space α) (𝒰: myset (myset α)): Prop :=
𝒰 ⊆ X.is_open ∧ ⋃₀ 𝒰 = myset.univ

def is_cover_l (X: topological_space α) (𝒰: mylist (myset α)): Prop :=
mylist.reduce_d myset.union ∅ 𝒰 = myset.univ

def is_compact (X: topological_space α): Prop :=
∀ 𝒰: myset (myset α), is_open_cover X 𝒰 →
∃ 𝒱: mylist (myset α), mylist.for_all 𝒰 𝒱 ∧ is_cover_l X 𝒱

theorem list_union (𝒰: mylist (myset α)):
mylist.reduce_d myset.union ∅ 𝒰 =
⋃₀ (λ x, 𝒰.contains x) :=
begin
  apply myset.setext,
  intro x,
  split; assume hx, {
    induction 𝒰 with U Us ih_U, {
      exfalso, from hx,
    }, {
      cases hx with hx hx, {
        existsi U,
        split, {
          left,
          from rfl,
        }, {
          assumption,
        },
      }, {
        cases ih_U hx with V hV,
        cases hV with hV hxV,
        existsi V,
        split, {
          right,
          from hV,
        }, {
          assumption,
        },
      },
    },
  }, {
    induction 𝒰 with U Us ih_U, {
      cases hx with V hV,
      cases hV with hV hxV,
      exfalso, from hV,
    }, {
      cases hx with V hV,
      cases hV with hV hxV,
      cases hV with hV hV, {
        left,
        rw ←hV,
        assumption,
      }, {
        right,
        apply ih_U,
        existsi V,
        split, {
          from hV,
        }, {
          assumption,
        },
      },
    },
  },
end

-- very suspicious
lemma list_intersection_iff_forall
(𝒱': mylist (myset α)) (y: α):
y ∈ mylist.reduce_d myset.intersection myset.univ 𝒱' ↔
mylist.for_all (λ V', y ∈ V') 𝒱' :=
begin
  split, {
    assume hyV',
    induction 𝒱' with V' V's ih_V's, {
      trivial,
    }, {
      split, {
        from hyV'.left,
      }, {
        apply ih_V's,
        from hyV'.right,
      },
    },
  }, {
    assume hyV',
    induction 𝒱' with V' V's ih_V's, {
      trivial,
    }, {
      split, {
        from hyV'.left,
      }, {
        apply ih_V's,
        from hyV'.right,
      },
    },
  },
end

lemma list_union_iff_exists
(𝒱': mylist (myset α)) (y: α):
y ∈ mylist.reduce_d myset.union ∅ 𝒱' ↔
mylist.for_some (λ V', y ∈ V') 𝒱' :=
begin
  split, {
    assume hyV',
    induction 𝒱' with V' V's ih_V's, {
      exfalso, from hyV',
    }, {
      cases hyV' with hyV' hyV', {
        left,
        from hyV',
      }, {
        right,
        apply ih_V's,
        from hyV',
      }
    },
  }, {
    assume hyV',
    induction 𝒱' with V' V's ih_V's, {
      exfalso, from hyV',
    }, {
      cases hyV' with hyV' hyV', {
        left,
        from hyV',
      }, {
        right,
        apply ih_V's,
        from hyV',
      }
    },
  },
end

-- not sure if this is optimal
-- but idk how else to construct things in the middle of a proof
private def is_raised_seq
(X: topological_space α) (Y: myset α)
(𝒰: myset (myset α)):
Π 𝒱: mylist (myset (subtype Y)),
Π (hWV: mylist.for_all ({W :
     myset (subtype Y) | (X.subspace_topology Y).is_open W ∧
     ∃ (U : myset α), Y.subtype_restriction U = W ∧ U ∈ 𝒰}) 𝒱),
mylist (myset α) → Prop
| mylist.empty _ mylist.empty := true
| mylist.empty _ (V' :: V's) := false
| (V :: Vs) _ mylist.empty := false
| (V :: Vs) h (V' :: V's) :=
  V' ∈ 𝒰 ∧
  (subspace_topology X Y).is_open V ∧
  myset.subtype_restriction Y V' = V ∧
  is_raised_seq Vs h.right V's

private lemma raised_seq_exists
(X: topological_space α) (Y: myset α) (𝒰: myset (myset α))
(𝒱: mylist (myset (subtype Y)))
(hWV: mylist.for_all ({W :
     myset (subtype Y) | (X.subspace_topology Y).is_open W ∧
     ∃ (U : myset α), Y.subtype_restriction U = W ∧ U ∈ 𝒰}) 𝒱)
(hVo: mylist.for_all (subspace_topology X Y).is_open 𝒱):
∃ 𝒱': mylist (myset α),
  is_raised_seq X Y 𝒰 𝒱 hWV 𝒱' :=
begin
  induction 𝒱 with V Vs ih_V, {
    existsi mylist.empty,
    trivial,
  }, {
    cases ih_V hWV.right hVo.right with V's hV's,
    cases hWV.left with V'' hV'',
    cases hV'' with V' hV',
    existsi V' :: V's,
    split, {
      from hV'.right,
    }, split, {
      assumption,
    }, split, {
      from hV'.left,
    }, {
      assumption,
    },
  },
end

private lemma raised_seq_U
(X: topological_space α) (Y: myset α) (𝒰: myset (myset α))
(𝒱: mylist (myset (subtype Y)))
(hWV: mylist.for_all ({W :
     myset (subtype Y) | (X.subspace_topology Y).is_open W ∧
     ∃ (U : myset α), Y.subtype_restriction U = W ∧ U ∈ 𝒰}) 𝒱)
(𝒱': mylist (myset α))
(hrV': is_raised_seq X Y 𝒰 𝒱 hWV 𝒱'):
mylist.for_all 𝒰 𝒱' :=
begin
  induction 𝒱' with V' V's ih_V' generalizing 𝒱, {
    trivial,
  }, {
    cases 𝒱 with V Vs, {
      exfalso, from hrV',
    }, {
      split, {
        from hrV'.left,
      }, {
        apply ih_V' _ hWV.right,
        from hrV'.right.right.right,
      },
    },
  },
end

private lemma raised_subset
(X: topological_space α) (Y: myset α) (𝒰: myset (myset α))
(𝒱: mylist (myset (subtype Y)))
(hWV: mylist.for_all ({W :
     myset (subtype Y) | (X.subspace_topology Y).is_open W ∧
     ∃ (U : myset α), Y.subtype_restriction U = W ∧ U ∈ 𝒰}) 𝒱)
(𝒱': mylist (myset α))
(hrV': is_raised_seq X Y 𝒰 𝒱 hWV 𝒱')
(x: α) (hYx: Y x)
(hxV: (⟨x, hYx⟩: subtype Y) ∈ mylist.reduce_d myset.union ∅ 𝒱):
x ∈ mylist.reduce_d myset.union ∅ 𝒱' :=
begin
  rw list_union,
  rw list_union at hxV,
  cases hxV with V hV,
  cases hV with hV hxV,
  have hWV' := hWV,
  rw mylist.for_all_iff_forall at hWV,
  cases (hWV V hV).right with V' hV',
  sorry,
end

theorem compact_subspace_cover
(X: topological_space α) (Y: myset α)
(hYcpct: is_compact (subspace_topology X Y)):
∀ 𝒰: myset (myset α),
  𝒰 ⊆ X.is_open →
  Y ⊆ ⋃₀ 𝒰 →
∃ 𝒱: mylist (myset α),
  mylist.for_all 𝒰 𝒱 ∧
  Y ⊆ mylist.reduce_d myset.union ∅ 𝒱 :=
begin
  intro 𝒰,
  assume hUo,
  assume hUYcov,
  let 𝒲 :=
      {W: myset (subtype Y) |
        (subspace_topology X Y).is_open W ∧
        ∃ U: myset α,
          Y.subtype_restriction U = W ∧
          U ∈ 𝒰},
  have step1 := hYcpct 𝒲,
  have hWYcov: (X.subspace_topology Y).is_open_cover 𝒲, {
    split, {
      intro W,
      assume hWW,
      from hWW.left,
    }, {
      apply myset.setext,
      intro x,
      split; assume hx, {
        trivial,
      }, {
        cases hUYcov x x.property with U hU,
        cases hU with hU hxU,
        existsi Y.subtype_restriction U,
        split, {
          split, {
            existsi U,
            split, {
              from hUo U hU,
            }, {
              refl,
            },
          }, {
            existsi U,
            split, {
              refl,
            }, {
              assumption,
            },
          },
        }, {
          from hxU,
        },
      },
    },
  },
  have step2 := step1 hWYcov,
  cases step2 with 𝒱 hV,
  have step4: mylist.for_all (X.subspace_topology Y).is_open 𝒱, {
    cases hV with hV garbage,
    clear garbage,
    induction 𝒱 with V Vs ih_V, {
      trivial,
    }, {
      split, {
        from hV.left.left,
      }, {
        apply ih_V, {
          from hV.right,
        },
      },
    },
  },
  have step3 := raised_seq_exists X Y 𝒰 𝒱 hV.left,
  have step5 := step3 step4,
  cases step5 with 𝒱' hV',
  existsi 𝒱',
  split, {
    apply raised_seq_U X Y 𝒰 𝒱 hV.left,
    from hV',
  }, {
    intro x,
    assume hYx,
    have hxV: (⟨x, hYx⟩: subtype Y) ∈ (mylist.reduce_d myset.union ∅ 𝒱), {
      have := hV.right,
      unfold is_cover_l at this,
      rw this,
      trivial,
    },
    from raised_subset X Y 𝒰 𝒱 hV.left 𝒱' hV' x hYx hxV,
  },
end

-- could do with a bit of splitting up imho
theorem closed_in_compact
(X: topological_space α) (U: myset α)
(hXcpct: is_compact X) (hUcl: X.is_closed U):
is_compact (subspace_topology X U) :=
begin
  intro 𝒰,
  assume hUcov,
  have step1 :=
      hXcpct
        ((myset.singleton U.compl) ∪
         {V | X.is_open V ∧
              𝒰 (U.subtype_restriction V)}),
  have hcovX:
      X.is_open_cover
        ((myset.singleton U.compl) ∪
         {V | X.is_open V ∧
              𝒰 (U.subtype_restriction V)}), {
    split, {
      intro S,
      assume hS,
      cases hS with hS hS, {
        rw myset.singleton_eq _ _ hS,
        from hUcl,
      }, {
        from hS.left,
      },
    }, {
      apply myset.setext,
      intro x,
      split; assume hx, {
        trivial,
      }, {
        by_cases hxU: x ∈ U, {
          have: (⟨x, hxU⟩: subtype U) ∈ ⋃₀ 𝒰, {
            rw hUcov.right,
            trivial,
          },
          cases this with V hV,
          cases hV with hV hxV,
          cases hUcov.left V hV with V' hV',
          existsi V',
          split, {
            right,
            split, {
              from hV'.left,
            }, {
              rw hV'.right at hV,
              from hV,
            },
          }, {
            rw hV'.right at hxV,
            from hxV,
          },
        }, {
          existsi U.compl,
          split, {
            left,
            from rfl,
          }, {
            from hxU,
          },
        },
      },
    },
  },
  have step2 := step1 hcovX,
  cases step2 with 𝒱 hV,
  existsi mylist.map
      (myset.subtype_restriction U)
      (mylist.filter (≠ U.compl) 𝒱),
  split, {
    cases hV with hV disaster,
    clear disaster,
    induction 𝒱 with V Vs ih_V, {
      trivial,
    }, {
      by_cases hVU: V = U.compl, {
        unfold mylist.filter,
        have: ¬(V ≠ U.compl), {
          assume h,
          contradiction,
        },
        rw if_neg this,
        apply ih_V,
        from hV.right,
      }, {
        unfold mylist.filter,
        rw if_pos hVU,
        split, {
          cases hV.left with hVl hVl, {
            contradiction,
          }, {
            from hVl.right,
          },
        }, {
          apply ih_V,
          from hV.right,
        },
      },
    },
  }, {
    cases hV with disaster hV,
    clear disaster,
    unfold is_cover_l,
    rw list_union,
    unfold is_cover_l at hV,
    rw list_union at hV,
    apply myset.setext,
    intro x,
    split; assume hx, {
      trivial,
    }, {
      have: x.val ∈ (myset.univ: myset α), {
        trivial,
      },
      rw ←hV at this,
      cases this with V hxV,
      cases hxV with hVV hxvV,
      existsi (myset.subtype_restriction U V),
      split, {
        apply mylist.contains_map,
        rw mylist.contains_filter,
        split, {
          from hVV,
        }, {
          assume hVUc,
          rw hVUc at hxvV,
          apply hxvV,
          from x.property,
        },
      }, {
        from hxvV,
      },
    },
  },
end

private def is_corresponding_subcov
(X: topological_space α) (x: α)
(U: myset α):
Π (𝒱: mylist (myset α)),
mylist (myset α) → Prop
| mylist.empty mylist.empty := true
| (V :: Vs) mylist.empty := false
| mylist.empty (V' :: V's) := false
| (V :: Vs) (V' :: V's) :=
  is_corresponding_subcov Vs V's ∧
  ∃ (y : α),
    y ∈ U ∧ X.is_open V ∧ X.is_open V' ∧ y ∈ V ∧ x ∈ V' ∧ V ∩ V' = ∅

private lemma corresponding_subcov_forall
(X: topological_space α) (x: α)
(U: myset α)
(𝒱: mylist (myset α))
(𝒱': mylist (myset α))
(hcor: is_corresponding_subcov X x U 𝒱 𝒱'):
mylist.for_all
  (λ V', ∃ (y : α),
    y ∈ U ∧ X.is_open V' ∧ x ∈ V')
  𝒱' :=
begin
  revert 𝒱,
  induction 𝒱' with V' V's ih_V's, {
    intros,
    trivial,
  }, {
    intros,
    cases 𝒱 with V Vs, {
      exfalso,
      from hcor,
    }, {
      split, {
        cases hcor.right with y hy,
        existsi y,
        repeat {split, cc},
        cc,
      }, {
        apply ih_V's Vs hcor.left,
      },
    },
  },
end

private lemma corresponding_subcov_forall2
(X: topological_space α) (x: α)
(U: myset α)
(𝒱: mylist (myset α))
(𝒱': mylist (myset α))
(hcor: is_corresponding_subcov X x U 𝒱 𝒱'):
mylist.for_all
  (λ V', ∃ (y : α) (V: myset α), mylist.contains V 𝒱 ∧
    y ∈ U ∧ X.is_open V ∧ X.is_open V' ∧ y ∈ V ∧ x ∈ V' ∧ V ∩ V' = ∅)
  𝒱' :=
begin
  revert 𝒱,
  induction 𝒱' with V' V's ih_V's, {
    intros,
    trivial,
  }, {
    intros,
    cases 𝒱 with V Vs, {
      exfalso,
      from hcor,
    }, {
      split, {
        cases hcor.right with y hy,
        existsi y,
        existsi V,
        repeat {split, cc},
        split, left,
        from rfl,
        from hy,
      }, {
        have := ih_V's Vs hcor.left,
        apply (mylist.for_all_iff_forall _ _).mpr,
        rw mylist.for_all_iff_forall at this,
        intro V'',
        assume hV''Vs,
        cases this V'' hV''Vs with y hy,
        cases hy with W hW,
        existsi y,
        existsi W,
        split, right,
        from hW.left,
        from hW.right,
        -- apply ih_V's Vs hcor.left,
      },
    },
  },
end

private lemma corresponding_subcov_forall2_symm
(X: topological_space α) (x: α)
(U: myset α)
(𝒱: mylist (myset α))
(𝒱': mylist (myset α))
(hcor: is_corresponding_subcov X x U 𝒱 𝒱'):
mylist.for_all
  (λ V, ∃ (y : α) (V': myset α), mylist.contains V' 𝒱' ∧
    y ∈ U ∧ X.is_open V ∧ X.is_open V' ∧ y ∈ V ∧ x ∈ V' ∧ V ∩ V' = ∅)
  𝒱 :=
begin
  revert 𝒱',
  induction 𝒱 with V Vs ih_Vs, {
    intros,
    trivial,
  }, {
    intros,
    cases 𝒱' with V' V's, {
      exfalso,
      from hcor,
    }, {
      split, {
        cases hcor.right with y hy,
        existsi y,
        existsi V',
        repeat {split, cc},
        split, left,
        from rfl,
        from hy,
      }, {
        have := ih_Vs V's hcor.left,
        apply (mylist.for_all_iff_forall _ _).mpr,
        rw mylist.for_all_iff_forall at this,
        intro V'',
        assume hV''Vs,
        cases this V'' hV''Vs with y hy,
        cases hy with W hW,
        existsi y,
        existsi W,
        split, right,
        from hW.left,
        from hW.right,
        -- apply ih_V's Vs hcor.left,
      },
    },
  },
end

private lemma exists_corresponding_subcov
(X: topological_space α) (x: α)
(U: myset α)
(𝒱: mylist (myset α))
(hVU: mylist.for_all
  ({V :
     myset α | ∃ (y : α) (W : myset α),
     y ∈ U ∧ X.is_open V ∧ X.is_open W ∧ y ∈ V ∧ x ∈ W ∧ V ∩ W = ∅}) 𝒱):
∃ 𝒱': mylist (myset α),
  is_corresponding_subcov X x U 𝒱 𝒱' :=
begin
  induction 𝒱 with V Vs ih_Vs, {
    existsi mylist.empty,
    trivial,
  }, {
    cases ih_Vs hVU.right with V's hV's,
    cases hVU.left with y hV',
    cases hV' with V' hV',
    existsi V' :: V's,
    split, {
      from hV's,
    }, {
      existsi y,
      from hV',
    },
  },
end

theorem compact_in_hausdorff
(X: topological_space α) (U: myset α)
(hUcpct: is_compact (subspace_topology X U))
(h_ausdorff: is_hausdorff X):
X.is_closed U :=
begin
  unfold is_closed,
  rw open_iff_neighbourhood_forall,
  intro x,
  assume hxUc,
  let 𝒰 :=
      {V: myset α | ∃ (y: α) (W: myset α),
        y ∈ U ∧
        X.is_open V ∧ X.is_open W ∧ y ∈ V ∧ x ∈ W ∧ V ∩ W = ∅},
  have step1 := compact_subspace_cover X U hUcpct 𝒰,
  have step2: 𝒰 ⊆ X.is_open, {
    intro V,
    assume hVU,
    cases hVU with y hVU,
    cases hVU with W hVU,
    from hVU.right.left,
  },
  have step3: U ⊆ ⋃₀ 𝒰, {
    intro y,
    assume hUy,
    cases h_ausdorff x y
      begin
        assume hxy,
        rw hxy at hxUc,
        contradiction,
      end with V1 hVy,
    cases hVy with V2 hVy,
    existsi V2,
    cases hVy,
    split, {
      existsi y,
      existsi V1,
      split, assumption,
      rw myset.intersection_comm,
      repeat {split},
      all_goals {assumption},
    }, {
      assumption,
    },
  },
  cases step1 step2 step3 with 𝒱 hV,
  cases exists_corresponding_subcov X x U 𝒱 hV.left with 𝒱' hV',
  have hV'fa := corresponding_subcov_forall X x U 𝒱 𝒱' hV',
  existsi mylist.reduce_d myset.intersection myset.univ 𝒱',
  split, {
    apply finite_open_intersection_open,
    rw mylist.for_all_iff_forall,
    intro V',
    assume hV'inV,
    rw mylist.for_all_iff_forall at hV,
    rw mylist.for_all_iff_forall at hV'fa,
    cases hV'fa V' hV'inV with _ h,
    from h.right.left,
  }, {
    cases hV with hV garbage,
    clear garbage,
    revert 𝒱,
    induction 𝒱' with V' V's ih_V's, {
      intros,
      trivial,
    }, {
      intros,
      split, {
        cases hV'fa.left with _ h,
        from h.right.right,
      }, {
        cases 𝒱 with V Vs, {
          exfalso,
          from hV',
        }, {
          apply ih_V's _ Vs, {
            from hV'.left,
          }, {
            from hV.right,
          }, {
            from hV'fa.right,
          },
        },
      },
    },
  }, {
    intro y,
    assume hV'y,
    assume hUy,
    have hV'fa2 := corresponding_subcov_forall2 X x U 𝒱 𝒱' hV',
    have := (list_union_iff_exists 𝒱 y).mp (hV.right y hUy),
    rw mylist.for_some_iff_exists at this,
    cases this with Z hZ,
    cases hZ with hZ hyZ,
    unfold mylist.contains at hZ,
    rw mylist.for_some_iff_exists at hZ,
    cases hZ with Z' hZ,
    cases hV with hVsubcovU hUsubV,
    rw mylist.for_all_iff_forall at hVsubcovU,
    have := hVsubcovU Z' hZ.left,
    cases this with z hz,
    cases hz with W hW,
    have hV'fa2s := corresponding_subcov_forall2_symm X x U 𝒱 𝒱' hV',
    rw mylist.for_all_iff_forall at hV'fa2s,
    cases hV'fa2s Z' hZ.left with z' hz',
    cases hz' with V' hzV',
    have := (list_intersection_iff_forall _ _).mp hV'y,
    rw mylist.for_all_iff_forall at this,
    have hyV' := this V' hzV'.left,
    suffices hhhhh: y ∈ Z' ∩ V', {
      rw hzV'.right.right.right.right.right.right at hhhhh,
      from hhhhh,
    }, {
      split, {
        rw ←hZ.right,
        assumption,
      }, {
        assumption,
      },
    },
  },
end

theorem surjective_image_compact
(X: topological_space α) (Y: topological_space β)
(hXcpct: is_compact X)
(f: α → β) (hcf: is_continuous X Y f) (hsurj: function.surjective f):
is_compact Y :=
begin
  intro 𝒰,
  assume hUcov,
  have step1 := hXcpct (myset.image (myset.inverse_image f) 𝒰),
  have step2: X.is_open_cover (myset.image (myset.inverse_image f) 𝒰), {
    split, {
      intro V,
      assume hV,
      cases hV with V' hV',
      rw ←hV'.right,
      apply hcf,
      apply hUcov.left,
      from hV'.left,
    }, {
      apply myset.setext,
      intro x,
      split; assume hx, {
        trivial,
      }, {
        have: f x ∈ ⋃₀ 𝒰, {
          rw hUcov.right,
          trivial,
        },
        cases this with U hU,
        cases hU with hU hfxU,
        existsi myset.inverse_image f U,
        split, {
          existsi U,
          split, {
            from hU,
          }, {
            refl,
          },
        }, {
          from hfxU,
        },
      },
    },
  },
  have step3 := step1 step2,
  cases step3 with 𝒱' hV',
  sorry,
end

theorem image_compact
(X: topological_space α) (Y: topological_space β)
(hXcpct: is_compact X)
(f: α → β) (hcf: is_continuous X Y f):
is_compact (Y.subspace_topology (myset.imageu f)) :=
begin
  apply surjective_image_compact X _ hXcpct (myset.function_restrict_to_image f), {
    apply continuous_to_image,
    from hcf,
  }, {
    intro y,
    cases y.property with x hx,
    existsi x,
    apply subtype.eq,
    from hx,
  },
end

-- could do with some theorems about preimages and images
-- of inverses
theorem topological_inverse_function_theorem
(X: topological_space α) (Y: topological_space β)
(hXcpct: is_compact X) (h_ausdorff: is_hausdorff Y)
(f: α → β) (g: β → α) (hcf: is_continuous X Y f)
(right_inv: f ∘ g = id)
(left_inv: g ∘ f = id):
is_continuous Y X g :=
begin
  rw continuous_iff_closed_preimage,
  intro U,
  assume hUo,
  have: myset.inverse_image g U = myset.image f U, {
    apply myset.setext,
    intro x,
    split; assume hx, {
      existsi g x,
      split, {
        from hx,
      }, {
        change (f ∘ g) x = x,
        rw right_inv,
        refl,
      },
    }, {
      cases hx with y hy,
      have: g x = y, {
        rw ←hy.right,
        change (g ∘ f) y = y,
        rw left_inv,
        refl,
      },
      rw ←this at hy,
      from hy.left,
    },
  },
  rw this, clear this,
  apply compact_in_hausdorff, {
    apply surjective_image_compact
        (X.subspace_topology U)
        (Y.subspace_topology (myset.image f U)) _
        (myset.function_restrict_to_set_image f U), {
      rw myset.restrict_to_set_image_composition,
      apply composition_continuous
          (X.subspace_topology U)
          (Y.subspace_topology (myset.imageu (λ x: subtype U, f x.val)))
          (Y.subspace_topology (myset.image f U)), {
        apply continuous_to_image,
        apply composition_continuous
            _ _ _ _ _ (inclusion_continuous _ _),
        from hcf,
      }, {
        intro U,
        assume hUo,
        cases hUo with V' hV',
        existsi V',
        split, {
          from hV'.left,
        }, {
          rw hV'.right,
          refl, -- thank the lord
        },
      },
    }, {
      intro y,
      cases y.property with y' hy',
      existsi (⟨y', hy'.left⟩: subtype U),
      apply subtype.eq,
      -- rw won't play ball >:(
      transitivity (f y'), {
        refl,
      }, {
        from hy'.right,
      },
    }, {
      apply closed_in_compact, {
        from hXcpct,
      }, {
        from hUo,
      },
    },
  }, {
    from h_ausdorff,
  },
end

end topological_space

end hidden


-- Copyright (c) 2017 Scott Morrison. All rights reserved.
-- Released under Apache 2.0 license as described in the file LICENSE.
-- Authors: Scott Morrison

import categories.universal

open category_theory
open category_theory.initial

namespace category_theory.universal

universes u v w

section
variables (C : Type u) [𝒞 : category.{u v} C]
include 𝒞

class has_InitialObject :=
  (initial_object : initial_object C)

class has_BinaryProducts :=
  (binary_product : Π X Y : C, BinaryProduct.{u v} X Y)
class has_FiniteProducts :=
  (product : Π {I : Type w} [fintype I] (f : I → C), Product.{u v w} f)

class has_TerminalObject :=
  (terminal_object : terminal_object C)

class has_BinaryCoproducts :=
  (binary_coproduct : Π X Y : C, BinaryCoproduct.{u v} X Y)
class has_FiniteCoproducts :=
  (coproduct : Π {I : Type w} [fintype I] (f : I → C), Coproduct.{u v w} f)

class has_ZeroObject :=
  (zero_object : zero_object C)

class has_Equalizers :=
  (equalizer : Π {X Y : C} (f g : X ⟶ Y), Equalizer f g)
class has_Coequalizers :=
  (coequalizer : Π {X Y : C} (f g : X ⟶ Y), Coequalizer f g)

def zero_object [has_ZeroObject.{u v} C] : C := has_ZeroObject.zero_object.{u v} C
def initial_object [has_InitialObject.{u v} C] : C := has_InitialObject.initial_object.{u v} C
def terminal_object [has_TerminalObject.{u v} C] : C := has_TerminalObject.terminal_object.{u v} C
end

section
variables {C : Type u} [𝒞 : category.{u v} C]
include 𝒞

section
variable [has_ZeroObject.{u v} C]
def zero_morphism (X Y : C) := ZeroObject.zero_morphism (has_ZeroObject.zero_object.{u v} C) X Y -- TODO provide a has_zero instance?

@[simp] lemma zero_morphism_left  {X Y Z : C} (f : Y ⟶ Z) : (zero_morphism X Y) ≫ f = zero_morphism X Z :=
begin
  -- TODO such a yucky proof
  unfold zero_morphism,
  unfold ZeroObject.zero_morphism,
  rw category.assoc,
  congr,
  apply ((has_ZeroObject.zero_object.{u v} C).is_initial).uniqueness,
end
@[simp] lemma zero_morphism_right {X Y Z : C} (f : X ⟶ Y) : f ≫ (zero_morphism Y Z) = zero_morphism X Z :=
begin
 -- TODO such a yucky proof
  unfold zero_morphism,
  unfold ZeroObject.zero_morphism,
  rw ← category.assoc,
  congr,
  apply ((has_ZeroObject.zero_object.{u v} C).is_terminal).uniqueness,
end
end

def binary_product [has_BinaryProducts.{u v} C] (X Y : C) := has_BinaryProducts.binary_product.{u v} X Y
def finite_product [has_FiniteProducts.{u v w} C] {I : Type w} [fin : fintype I] (f : I → C) := @has_FiniteProducts.product.{u v w} C _ _ I fin f

def binary_coproduct [has_BinaryCoproducts.{u v} C] (X Y : C) := has_BinaryCoproducts.binary_coproduct.{u v} X Y
def finite_coproduct [has_FiniteCoproducts.{u v w} C] {I : Type w} [fin : fintype I] (f : I → C) := @has_FiniteCoproducts.coproduct.{u v w} C _ _ I fin f

def equalizer [has_Equalizers.{u v} C] {X Y : C} (f g : X ⟶ Y) := has_Equalizers.equalizer.{u v} f g
def coequalizer [has_Coequalizers.{u v} C] {X Y : C} (f g : X ⟶ Y) := has_Coequalizers.coequalizer.{u v} f g
end

section

class has_Products (C : Type (u+1)) [large_category C] :=
  (product : Π {I : Type u} (f : I → C), Product.{u+1 u u} f)
class has_Coproducts (C : Type (u+1)) [large_category C] :=
  (coproduct : Π {I : Type u} (f : I → C), Coproduct.{u+1 u u} f)

variables {C : Type (u+1)} [large_category C]

def product [has_Products C] {I : Type u} (F : I → C) := has_Products.product F
def coproduct [has_Coproducts C] {I : Type u} (F : I → C) := has_Coproducts.coproduct F
end

end category_theory.universal


import hott.prop_trunc hott.init hott.types.trunc hott.homotopy.susp prop_logic

universes u v w
hott_theory

namespace hott
open hott.is_trunc hott.trunc hott.equiv hott.is_equiv hott.sigma hott.susp hott.sum ulift

set_option pp.universes true

/- Looks like a HoTT-ism but is a way to construct sets - which can be interpreted
   as subsets. Therefore it is also contained as [is_set_pred] in [subset.lean].
   But we want to avoid dependencies.  -/
@[hott, instance]
def Sigma_Set_Prop_is_set (X : Set) (P : X -> Prop) :
  is_set (Σ x : X, P x) :=
have forall (x : X), is_set (P x), from
  assume x,
  have is_prop (P x), from trunctype.struct (P x),
  is_trunc_succ (P x) -1,
is_trunc_sigma (λ x : X, ↥(P x)) 0

@[hott, instance]
def fib_set_map_is_set {A B : Set} (f : A -> B) (b : B) : is_set (fiber f b) :=
  have sig_fib_is_set : is_set (Σ a : A, f a = b), from
    let P := λ a : A, Prop.mk (f a = b) (is_prop.mk (@is_set.elim _ _ (f a) b)) in
    Sigma_Set_Prop_is_set A P,
  is_trunc_equiv_closed_rev 0 (fiber.sigma_char f b) sig_fib_is_set

/- The two versions of the Axiom of Choice, as presented in the HoTT-Book, (3.8.1)
   and (3.8.3). We postulate both as axioms, to keep track of their use, even if by
   Lem.3.8.2, the two are equivalent. -/
@[hott]
def Choice := Π (X : Set.{u}) (A : X -> Set.{v})
  (P: Π x : X, A x -> trunctype.{v} -1),
(forall x : X, ∥ Σ a : A x, P x a ∥) -> ∥ Σ (g : Π x : X, A x), Π x : X, P x (g x) ∥

@[hott]
axiom AC : Choice

@[hott]
def Choice_nonempty := Π (X : Set.{u}) (Y : X -> Set.{v}),
  (Π x : X, ∥ Y x ∥) -> ∥ Π x : X, Y x ∥

@[hott]
axiom AC_nonempty : Choice_nonempty

@[hott]
def AxChoice_equiv : Choice.{u v} ↔ Choice_nonempty.{u v} :=
  have imp1 : Choice.{u v} -> Choice_nonempty.{u v}, from
    assume AxCh, assume X Y pi_trunc,
    have pi_trunc_T : Π x : X, ∥ Σ y : Y x, True ∥, from
      assume x, trunc_functor -1 (λ y : Y x, ⟨y,true.intro⟩) (pi_trunc x),
    have trunc_sigma_T : ∥ Σ (g : Π x : X, Y x), ∀ x : X, True ∥, from
      AxCh X Y (λ x : X, λ y : Y x, True) pi_trunc_T,
    have sigma_pi_T : (Σ (g : Π x : X, Y x), ∀ x : X, True) -> (Π x : X, Y x), from
      assume sigma_T, sigma_T.1,
    trunc_functor -1 sigma_pi_T trunc_sigma_T,
  have imp2 : Choice_nonempty.{u v} -> Choice.{u v}, from
    assume AxCh_ne, assume X A P pi_trunc,
    have sigma_P_is_set : Π x : X, is_set (Σ (a : A x), P x a), from
      assume x, Sigma_Set_Prop_is_set (A x) (P x),
    let Sigma_P := λ x, Set.mk (Σ (a : A x), P x a) (sigma_P_is_set x) in
    have trunc_pi : ∥ Π (x : X), Σ (a : A x), P x a ∥, from
      AxCh_ne X Sigma_P pi_trunc,
    have pssp : (Π (x : X), Σ (a : A x), P x a) -> (Σ (g : Π x : X, A x), Π x : X, P x (g x)), from
      (sigma_pi_equiv_pi_sigma (λ (x : X) (a : A x), ↥(P x a)))⁻¹ᶠ,
    trunc_functor -1 pssp trunc_pi,
  (imp1, imp2)

/- We follow the proof of Diaconescu (HoTT-book, Thm.10.1.14) to show that the Axiom
   of Choice (in its second version [AxChoice_nonempty]) implies the Law of the Excluded
   Middle.

   The first step is a lemma stating that the suspension (see HoTT-book, Sec.6.5) of a
   mere proposition is a set. For its proof we use the criterion [refl_rel_set]
   (HoTT-book, Thm.7.2.2) and construct a reflexive relation using double induction
   on [⅀ A]. This is a lot of work since [⅀ A] is a HIT, and we have to check equalities
   and equalities of equalities.

   We need the criterion [refl_rel_set] to show that a type is a set. This is
   Thm.7.2.2 in the HoTT-book. Its proof requires a lemma.
   Should be in [path.lean]. -/
@[hott, hsimp]
def concat_eq_tr_eq : Π {A : Type _} {a b c: A} (p : a = c) (u : b = a),
  u ⬝ p = p ▸ u :=
by intros A a b c p u; induction p; hsimp

@[hott]
def refl_rel_set : Π {X : Type _} (R : X -> X -> Type _)
     (mere_rel : ∀ x y, is_prop (R x y)) (refl_rel : Π x, R x x)
     (rel_id : Π x y, R x y -> x = y), (is_set X) × (∀ x y, R x y ≃ x = y) :=
assume X R mere_rel refl_rel rel_id,
have X_is_set : is_set X, from
  have X_has_K : Π (x : X) (p : x = x), p = rfl, from
    assume x p,
    have eq_tr : p ▸[λ y : X, R x y] (refl_rel x) = refl_rel x, from
      is_prop.elim' _ _ (mere_rel x x),
    have tr_rel_id : Π {y z : X} (p : y = z) (r : R x y),
            p ▸[λ w : X, x = w] (rel_id x y r) = rel_id x z (p ▸ r), from
      by intros x y p; induction p; intro r; hsimp,
    have eq_rel : rel_id x x (refl_rel x) ⬝ p = rel_id x x (refl_rel x), from
      calc rel_id x x (refl_rel x) ⬝ p =
                                  (p ▸[λ a : X, x = a] rel_id x x (refl_rel x)) :
           concat_eq_tr_eq p (rel_id x x (refl_rel x))
           ... = rel_id x x (p ▸[λ y : X, R x y] refl_rel x) :
           tr_rel_id p (refl_rel x)
           ... = rel_id x x (refl_rel x) : by rwr eq_tr,
    eq_idp_of_con_left eq_rel,
  have X_has_UIP : Π (x y : X) (p q : x = y), p = q, by
    intros x y p q; induction q; exact X_has_K x p, /- This is Thm.7.2.1. -/
  have X_eq_is_prop : Π (x y : X), is_prop (x = y), from
    assume x y, is_prop.mk (X_has_UIP x y),
  is_trunc_succ_intro X_eq_is_prop,
have rel_equiv_id : ∀ x y, R x y ≃ x = y, from
  assume x y,
  have eq_prop : is_prop (x = y), from is_prop.mk (@is_set.elim X X_is_set x y),
  have rel_eq : R x y -> x = y, from rel_id x y,
  have eq_rel : x = y -> R x y, from assume p, p ▸[λ w : X, R x w] (refl_rel x),
  @prop_iff_equiv _ _ (mere_rel x y) eq_prop (rel_eq, eq_rel),
(X_is_set, rel_equiv_id)

/- We define the relation on [⅀ A × ⅀ A] using double induction. -/
@[hott]
def susp_of_prop_rel (A : Type _) [HA : is_prop A] : ⅀ A -> ⅀ A -> Type _ :=
let P := λ x y : ⅀ A, Type _ in
begin
  intros x y,
  hinduction x using susp.rec with a,
    hinduction y using susp.rec,
      exact One,
      exact A,
      exact inhabited_prop_po One A (merid a) One.star a,
    hinduction y using susp.rec,
      exact A,
      exact One,
      exact inhabited_prop_po A One (merid a) a One.star,
    hinduction y using susp.rec with b,
      change One =[merid a; λ (x' : ↥(⅀ A)), Type _] A,
        exact inhabited_prop_po One A (merid a) One.star a,
      change A =[merid a; λ (x' : ↥(⅀ A)), Type _] One,
        exact inhabited_prop_po A One (merid a) a One.star,
      have is_prop_P_Sm : ∀ a : A, is_prop (A =[merid a; λ x : ⅀ A, P x south] One), from
        begin intro a', exact po_is_prop (merid a') end, --assume a, po_is_prop (merid a),
      exact @po_proofs _ _ _ (merid b) (λ y : ⅀ A,
              @susp.rec _ (P north) One A (λ a : A, inhabited_prop_po One A (merid a) One.star a) y
                  =[merid a; λ (x : ⅀ A), P x y]
              @susp.rec _ (P south) A One (λ a : A, inhabited_prop_po A One (merid a) a One.star) y)
                   (is_prop_P_Sm a) _ _
end

/- The relation on [⅀ A × ⅀ A] is a mere relation. -/
@[hott]
def susp_of_prop_rel_is_mere_rel (A : Type _) [is_prop A] :
  ∀ x y : ⅀ A, is_prop (susp_of_prop_rel A x y) :=
have NN_eq : susp_of_prop_rel A north north = One, from rfl,
have NS_eq : susp_of_prop_rel A north south = A, from rfl,
have SN_eq : susp_of_prop_rel A south north = A, from rfl,
have SS_eq : susp_of_prop_rel A south south = One, from rfl,
begin
  intros x y,
  hinduction x using susp.rec,
    hinduction y using susp.rec,
      rwr NN_eq, exact One_is_prop,
      rwr NS_eq, assumption,
      apply pathover_of_tr_eq, apply is_prop.elim,
    hinduction y using susp.rec,
      rwr SN_eq, assumption,
      rwr SS_eq, exact One_is_prop,
      apply pathover_of_tr_eq, apply is_prop.elim,
    apply pathover_of_tr_eq, apply is_prop.elim,
end

/- The relation on [⅀ A × ⅀ A] is a reflexive. -/
@[hott]
def susp_of_prop_rel_is_refl (A : Type _) [is_prop A] :
  Π x : ⅀ A, susp_of_prop_rel A x x :=
begin
  have NN_eq : susp_of_prop_rel A north north = One, from rfl,
  have SS_eq : susp_of_prop_rel A south south = One, from rfl,
  intro x,
  hinduction x using susp.rec,
    exact One.star,
    exact One.star,
    apply pathover_of_tr_eq,
    exact @is_prop.elim _ (susp_of_prop_rel_is_mere_rel A south south) _ _,
end

/- The next lemmas should be in [init.pathover.lean] and [init.path.lean]. -/
@[hott]
def fn_pathover_eval {A : Type u} {a b : A} (e : a = b) {P Q : A -> Type v}
  {ta : P a -> Q a} : ∀ x : P b, (e ▸ ta) x = e ▸ ta (e⁻¹ ▸ x) :=
begin hinduction e, hsimp, intro x, exact idp end

@[hott]
def tr_eq_inv_con {A : Type u} {a b c: A} (e : a = b) (f : a = c) : e ▸[λ d, d = c] f = e⁻¹ ⬝ f :=
  begin hinduction e, apply tr_eq_of_pathover,
                apply pathover_idp_of_eq, rwr idp_inv, rwr idp_con end

@[hott]
def tr_fn_eq {A : Type u} {B : Type v} {a b : A} (p : a = b) {P Q : A -> B} (q : P a = Q a) :
  p ▸[λ c, P c = Q c] q = (ap P p)⁻¹ ⬝ q ⬝ (ap Q p) :=
begin
  hinduction p, apply tr_eq_of_pathover, apply pathover_idp_of_eq, rwr ap_idp, rwr ap_idp,
  rwr idp_inv, rwr idp_con,
end

/- Construction of identies from relations of north and of south. -/
@[hott, hsimp]
def PN_ {A : Type _} [pA : is_prop A] :
  ∀ y : ⅀ A, susp_of_prop_rel A north y -> north = y :=
begin
  intro y,
  hinduction y using susp.rec,
    intro, refl,
    intro a, exact merid a,
    apply pathover_of_tr_eq, apply eq_of_homotopy,
      intro a', rwr fn_pathover_eval, change (refl north) ⬝ merid a = merid a',
      rwr idp_con, apply ap, apply is_prop.elim,
end

@[hott, hsimp]
def PS_ {A : Type _} [pA : is_prop A] :
  ∀ y : ⅀ A, susp_of_prop_rel A south y -> south = y :=
begin
  intro y,
  hinduction y using susp.rec,
    intro a', exact (merid a')⁻¹,
    intro, refl,
    apply pathover_of_tr_eq, apply eq_of_homotopy, intro s, rwr fn_pathover_eval,
      change (merid ((merid a)⁻¹ ▸ s))⁻¹ ⬝ (merid a) = refl south,
      apply inverse, apply idp_eq_inv_con, apply ap, apply @is_prop.elim _ pA _ _,
end

/- We need to construct paths between the functions [PN_] and [PS_] over meridians and then show
  equality of these paths. This works easily if we construct the second path as a transport of the
  first path via a meridian, and meridians are equal. -/
@[hott]
def Q_N {A : Type _} [pA : is_prop A] (a : A) :
  PN_ north =[merid a; λ (x' : ↥(⅀ A)), susp_of_prop_rel A x' north → x' = north] PS_ north :=
begin
  apply pathover_of_tr_eq, apply eq_of_homotopy, intro a', rwr fn_pathover_eval,
  change merid a ▸[λ x, x = north] refl north = (merid a')⁻¹, rwr tr_eq_inv_con,
  rwr con_idp, apply ap (λ a : A, (merid a)⁻¹), apply is_prop.elim
end

@[hott]
def Q_S {A : Type _} [pA : is_prop A] (a : A) :
  PN_ south =[merid a; λ (x' : ↥(⅀ A)), susp_of_prop_rel A x' south → x' = south] PS_ south :=
merid a▸[λ {a_1 : ↥(⅀ A)},
        PN_ a_1 =[merid a; λ (x' : ↥(⅀ A)), susp_of_prop_rel A x' a_1 → x' = a_1] PS_ a_1]Q_N a

@[hott]
def susp_of_prop_rel_id (A : Type _) [pA : is_prop A] :
  ∀ x y : ⅀ A, susp_of_prop_rel A x y -> x = y :=
let R := λ x y : ⅀ A, susp_of_prop_rel A x y in
begin
  intros x y,
  hinduction x using susp.rec,
    exact PN_ y,
    exact PS_ y,
    hinduction y using susp.rec with a₁,
      exact Q_N a,
      exact Q_S a,
      have merid_eq : merid a₁ = merid a, from
        begin apply ap, apply is_prop.elim end,
      apply pathover_of_tr_eq, rwr merid_eq
end

@[hott]
def susp_of_prop_is_set (A : Type _) [is_prop A] : is_set (⅀ A) :=
  (refl_rel_set (susp_of_prop_rel A) (susp_of_prop_rel_is_mere_rel A)
   (susp_of_prop_rel_is_refl A) (susp_of_prop_rel_id A)).1

/- Using the second part of Thm.7.2.2 = [refl_rel_set] we can show a corollary: -/
@[hott]
def merid_equiv_A (A : Type _) [is_prop A] : (@north A = south) ≃ A :=
  have A_equiv_merid : A ≃ (@north A = south), from
    (refl_rel_set (susp_of_prop_rel A) (susp_of_prop_rel_is_mere_rel A)
                   (susp_of_prop_rel_is_refl A) (susp_of_prop_rel_id A)).2 north south,
  A_equiv_merid⁻¹ᵉ

/- This is a simplified version of closedness under equivalences for decidability. -/
@[hott]
def iff_LEM_LEM {A B : Type u} : (A ↔ B) -> (A ⊎ ¬ A) -> (B ⊎ ¬ B) :=
  assume AB_iff, assume A_dec,
  begin
    hinduction A_dec with a na,
      exact sum.inl (AB_iff.1 a),
      apply sum.inr, assume b, exact empty.elim (na (AB_iff.2 b))
  end

/- Diaconescu's Theorem, HoTT-book, [Thm.10.1.14] -/
@[hott]
def AC_implies_LEM : Choice_nonempty.{u} -> ExcludedMiddle.{u} :=
  assume AC, assume A,
  let f := @Two.rec (λ t : Two.{u}, ⅀ A) (@north A) (@south A) in
  have zn : f Two.zero = north, by refl,
  have zs : f Two.one = south, by refl,
  have f_is_surj : @is_surjective Two (⅀ A) f, from
    have PN : @image Two (⅀ A) f north, from @image.mk Two (⅀ A) f _ Two.zero zn,
    have PS : @image Two (⅀ A) f south, from @image.mk Two (⅀ A) f _ Two.one zs,
    have Pm : Π a : A, PN =[merid a; λ x : susp A, @image Two (⅀ A) f x] PS, from
      assume a, pathover_prop_eq _ (merid a) PN PS,
    assume x, susp.rec PN PS Pm x,
  let X := Set.mk (⅀ A) (susp_of_prop_is_set.{u u} A),
      Y := λ x : X, Set.mk (@fiber Two (⅀ A) f x) (@fib_set_map_is_set Two_Set X f x) in
  have mere_g : ∥ Π x : X, @fiber Two (⅀ A) f x ∥, from AC X Y f_is_surj,
  have g_inv_f : (Π x : X, @fiber Two (⅀ A) f x) ->
                              (Σ (g : ⅀ A -> Two), Π (x : ⅀ A), f (g x) = x), from
    assume g, ⟨λ x : ⅀ A, (g x).1, λ x : ⅀ A, (g x).2 ⟩,
  have g_f_dec : Π (g : ⅀ A -> Two), (g (f Two.zero) = g (f Two.one)) ⊎
                                          ¬ (g (f Two.zero) = g (f Two.one)), from
    assume g, Two_is_decidable (g (f Two.zero)) (g (f Two.one)),
  have f_dec : (Π x : X, @fiber Two (⅀ A) f x) -> (f Two.zero = f Two.one) ⊎
                                                  ¬ (f Two.zero = f Two.one), from
    assume sect_f, let g := (g_inv_f sect_f).1, gf_dec := g_f_dec g in
    have g_inj : ∀ x y : ⅀ A, g x = g y -> x = y, from
      assume x y g_eq,
      have fg_eq : f (g x) = f (g y), by rwr g_eq,
      ((g_inv_f sect_f).2 x)⁻¹ ⬝ fg_eq ⬝ ((g_inv_f sect_f).2 y),
    have gf01_f01_eq : g (f Two.zero) = g (f Two.one) -> f Two.zero = f Two.one, from
      assume gf_eq,
      have fgf_eq : f (g (f Two.zero)) = f (g (f Two.one)), by rwr gf_eq,
      ((g_inv_f sect_f).2 (f Two.zero))⁻¹ ⬝ fgf_eq ⬝ ((g_inv_f sect_f).2 (f Two.one)),
    have f01_gf01_eq : f Two.zero = f Two.one -> g (f Two.zero) = g (f Two.one), from
      begin intro f_eq, rwr f_eq end,
    iff_LEM_LEM ⟨gf01_f01_eq, f01_gf01_eq⟩ (g_f_dec g),
  have A_dec :  (Π x : X, @fiber Two (⅀ A) f x) -> A ⊎ ¬ A, from
    assume sect_f,
    have NS_eq_dec : (@north A = south) ⊎ ¬ (@north A = south), from
      begin rwr zn⁻¹, rwr zs⁻¹, exact f_dec sect_f end,
    have NS_eq_iff_A : (@north A = south) ↔ A, from
      ⟨merid_equiv_A.{u u} A, (merid_equiv_A.{u u} A)⁻¹ᶠ⟩,
    iff_LEM_LEM NS_eq_iff_A NS_eq_dec,
  have trunc_LEM : ∥ A ⊎ ¬ A ∥, from trunc_functor -1 A_dec mere_g,
  @untrunc_of_is_trunc _ _ is_prop_LEM trunc_LEM

end hott