/-
Copyright (c) 2019 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison, Bhavik Mehta
-/
import category_theory.limits.shapes.terminal
import category_theory.discrete_category
import category_theory.epi_mono
import category_theory.over

/-!
# Binary (co)products

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

We define a category `walking_pair`, which is the index category
for a binary (co)product diagram. A convenience method `pair X Y`
constructs the functor from the walking pair, hitting the given objects.

We define `prod X Y` and `coprod X Y` as limits and colimits of such functors.

Typeclasses `has_binary_products` and `has_binary_coproducts` assert the existence
of (co)limits shaped as walking pairs.

We include lemmas for simplifying equations involving projections and coprojections, and define
braiding and associating isomorphisms, and the product comparison morphism.

## References
* [Stacks: Products of pairs](https://stacks.math.columbia.edu/tag/001R)
* [Stacks: coproducts of pairs](https://stacks.math.columbia.edu/tag/04AN)
-/

noncomputable theory

universes v u u₂

open category_theory

namespace category_theory.limits

/-- The type of objects for the diagram indexing a binary (co)product. -/
@[derive decidable_eq, derive inhabited]
inductive walking_pair : Type
| left | right

open walking_pair

/--
The equivalence swapping left and right.
-/
def walking_pair.swap : walking_pair ≃ walking_pair :=
{ to_fun := λ j, walking_pair.rec_on j right left,
  inv_fun := λ j, walking_pair.rec_on j right left,
  left_inv := λ j, by { cases j; refl, },
  right_inv := λ j, by { cases j; refl, }, }

@[simp] lemma walking_pair.swap_apply_left : walking_pair.swap left = right := rfl
@[simp] lemma walking_pair.swap_apply_right : walking_pair.swap right = left := rfl
@[simp] lemma walking_pair.swap_symm_apply_tt : walking_pair.swap.symm left = right := rfl
@[simp] lemma walking_pair.swap_symm_apply_ff : walking_pair.swap.symm right = left := rfl

/--
An equivalence from `walking_pair` to `bool`, sometimes useful when reindexing limits.
-/
def walking_pair.equiv_bool : walking_pair ≃ bool :=
{ to_fun := λ j, walking_pair.rec_on j tt ff, -- to match equiv.sum_equiv_sigma_bool
  inv_fun := λ b, bool.rec_on b right left,
  left_inv := λ j, by { cases j; refl, },
  right_inv := λ b, by { cases b; refl, }, }

@[simp] lemma walking_pair.equiv_bool_apply_left : walking_pair.equiv_bool left = tt := rfl
@[simp] lemma walking_pair.equiv_bool_apply_right : walking_pair.equiv_bool right = ff := rfl
@[simp] lemma walking_pair.equiv_bool_symm_apply_tt : walking_pair.equiv_bool.symm tt = left := rfl
@[simp] lemma walking_pair.equiv_bool_symm_apply_ff : walking_pair.equiv_bool.symm ff = right := rfl

variables {C : Type u}

/-- The function on the walking pair, sending the two points to `X` and `Y`. -/
def pair_function (X Y : C) : walking_pair → C := λ j, walking_pair.cases_on j X Y

@[simp] lemma pair_function_left (X Y : C) : pair_function X Y left = X := rfl
@[simp] lemma pair_function_right (X Y : C) : pair_function X Y right = Y := rfl

variables [category.{v} C]

/-- The diagram on the walking pair, sending the two points to `X` and `Y`. -/
def pair (X Y : C) : discrete walking_pair ⥤ C :=
discrete.functor (λ j, walking_pair.cases_on j X Y)

@[simp] lemma pair_obj_left (X Y : C) : (pair X Y).obj ⟨left⟩ = X := rfl
@[simp] lemma pair_obj_right (X Y : C) : (pair X Y).obj ⟨right⟩ = Y := rfl

section
variables {F G : discrete walking_pair ⥤ C} (f : F.obj ⟨left⟩ ⟶ G.obj ⟨left⟩)
  (g : F.obj ⟨right⟩ ⟶ G.obj ⟨right⟩)

local attribute [tidy] tactic.discrete_cases

/-- The natural transformation between two functors out of the
 walking pair, specified by its
components. -/
def map_pair : F ⟶ G := { app := λ j, discrete.rec_on j (λ j, walking_pair.cases_on j f g) }

@[simp] lemma map_pair_left : (map_pair f g).app ⟨left⟩ = f := rfl
@[simp] lemma map_pair_right : (map_pair f g).app ⟨right⟩ = g := rfl

/-- The natural isomorphism between two functors out of the walking pair, specified by its
components. -/
@[simps]
def map_pair_iso (f : F.obj ⟨left⟩ ≅ G.obj ⟨left⟩) (g : F.obj ⟨right⟩ ≅ G.obj ⟨right⟩) : F ≅ G :=
nat_iso.of_components (λ j, discrete.rec_on j (λ j, walking_pair.cases_on j f g)) (by tidy)

end

/-- Every functor out of the walking pair is naturally isomorphic (actually, equal) to a `pair` -/
@[simps]
def diagram_iso_pair (F : discrete walking_pair ⥤ C) :
  F ≅ pair (F.obj ⟨walking_pair.left⟩) (F.obj ⟨walking_pair.right⟩) :=
map_pair_iso (iso.refl _) (iso.refl _)

section
variables {D : Type u} [category.{v} D]

/-- The natural isomorphism between `pair X Y ⋙ F` and `pair (F.obj X) (F.obj Y)`. -/
def pair_comp (X Y : C) (F : C ⥤ D) : pair X Y ⋙ F ≅ pair (F.obj X) (F.obj Y) :=
diagram_iso_pair _

end

/-- A binary fan is just a cone on a diagram indexing a product. -/
abbreviation binary_fan (X Y : C) := cone (pair X Y)

/-- The first projection of a binary fan. -/
abbreviation binary_fan.fst {X Y : C} (s : binary_fan X Y) := s.π.app ⟨walking_pair.left⟩

/-- The second projection of a binary fan. -/
abbreviation binary_fan.snd {X Y : C} (s : binary_fan X Y) := s.π.app ⟨walking_pair.right⟩

@[simp] lemma binary_fan.π_app_left {X Y : C} (s : binary_fan X Y) :
  s.π.app ⟨walking_pair.left⟩ = s.fst := rfl
@[simp] lemma binary_fan.π_app_right {X Y : C} (s : binary_fan X Y) :
  s.π.app ⟨walking_pair.right⟩ = s.snd := rfl

/-- A convenient way to show that a binary fan is a limit. -/
def binary_fan.is_limit.mk {X Y : C} (s : binary_fan X Y)
  (lift : Π {T : C} (f : T ⟶ X) (g : T ⟶ Y), T ⟶ s.X)
  (hl₁ : ∀ {T : C} (f : T ⟶ X) (g : T ⟶ Y), lift f g ≫ s.fst = f)
  (hl₂ : ∀ {T : C} (f : T ⟶ X) (g : T ⟶ Y), lift f g ≫ s.snd = g)
  (uniq : ∀ {T : C} (f : T ⟶ X) (g : T ⟶ Y) (m : T ⟶ s.X) (h₁ : m ≫ s.fst = f)
    (h₂ : m ≫ s.snd = g), m = lift f g) : is_limit s := is_limit.mk
  (λ t, lift (binary_fan.fst t) (binary_fan.snd t))
  (by { rintros t (rfl|rfl), { exact hl₁ _ _ }, { exact hl₂ _ _ } })
  (λ t m h, uniq _ _ _ (h ⟨walking_pair.left⟩) (h ⟨walking_pair.right⟩))

lemma binary_fan.is_limit.hom_ext {W X Y : C} {s : binary_fan X Y} (h : is_limit s)
  {f g : W ⟶ s.X} (h₁ : f ≫ s.fst = g ≫ s.fst) (h₂ : f ≫ s.snd = g ≫ s.snd) : f = g :=
h.hom_ext $ λ j, discrete.rec_on j (λ j, walking_pair.cases_on j h₁ h₂)

/-- A binary cofan is just a cocone on a diagram indexing a coproduct. -/
abbreviation binary_cofan (X Y : C) := cocone (pair X Y)

/-- The first inclusion of a binary cofan. -/
abbreviation binary_cofan.inl {X Y : C} (s : binary_cofan X Y) := s.ι.app ⟨walking_pair.left⟩

/-- The second inclusion of a binary cofan. -/
abbreviation binary_cofan.inr {X Y : C} (s : binary_cofan X Y) := s.ι.app ⟨walking_pair.right⟩

@[simp] lemma binary_cofan.ι_app_left {X Y : C} (s : binary_cofan X Y) :
  s.ι.app ⟨walking_pair.left⟩ = s.inl := rfl
@[simp] lemma binary_cofan.ι_app_right {X Y : C} (s : binary_cofan X Y) :
  s.ι.app ⟨walking_pair.right⟩ = s.inr := rfl

/-- A convenient way to show that a binary cofan is a colimit. -/
def binary_cofan.is_colimit.mk {X Y : C} (s : binary_cofan X Y)
  (desc : Π {T : C} (f : X ⟶ T) (g : Y ⟶ T), s.X ⟶ T)
  (hd₁ : ∀ {T : C} (f : X ⟶ T) (g : Y ⟶ T), s.inl ≫ desc f g = f)
  (hd₂ : ∀ {T : C} (f : X ⟶ T) (g : Y ⟶ T), s.inr ≫ desc f g = g)
  (uniq : ∀ {T : C} (f : X ⟶ T) (g : Y ⟶ T) (m : s.X ⟶ T) (h₁ : s.inl ≫ m = f)
    (h₂ : s.inr ≫ m = g), m = desc f g) : is_colimit s := is_colimit.mk
    (λ t, desc (binary_cofan.inl t) (binary_cofan.inr t))
    (by { rintros t (rfl|rfl), { exact hd₁ _ _ }, { exact hd₂ _ _ }})
    (λ t m h, uniq _ _ _ (h ⟨walking_pair.left⟩) (h ⟨walking_pair.right⟩))

lemma binary_cofan.is_colimit.hom_ext {W X Y : C} {s : binary_cofan X Y} (h : is_colimit s)
  {f g : s.X ⟶ W} (h₁ : s.inl ≫ f = s.inl ≫ g) (h₂ : s.inr ≫ f = s.inr ≫ g) : f = g :=
h.hom_ext $ λ j, discrete.rec_on j (λ j, walking_pair.cases_on j h₁ h₂)

variables {X Y : C}

section
local attribute [tidy] tactic.discrete_cases

/-- A binary fan with vertex `P` consists of the two projections `π₁ : P ⟶ X` and `π₂ : P ⟶ Y`. -/
@[simps X]
def binary_fan.mk {P : C} (π₁ : P ⟶ X) (π₂ : P ⟶ Y) : binary_fan X Y :=
{ X := P,
  π := { app := λ j, discrete.rec_on j (λ j, walking_pair.cases_on j π₁ π₂) }}

/-- A binary cofan with vertex `P` consists of the two inclusions `ι₁ : X ⟶ P` and `ι₂ : Y ⟶ P`. -/
@[simps X]
def binary_cofan.mk {P : C} (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) : binary_cofan X Y :=
{ X := P,
  ι := { app := λ j, discrete.rec_on j (λ j, walking_pair.cases_on j ι₁ ι₂) }}

end

@[simp] lemma binary_fan.mk_fst {P : C} (π₁ : P ⟶ X) (π₂ : P ⟶ Y) :
  (binary_fan.mk π₁ π₂).fst = π₁ := rfl
@[simp] lemma binary_fan.mk_snd {P : C} (π₁ : P ⟶ X) (π₂ : P ⟶ Y) :
  (binary_fan.mk π₁ π₂).snd = π₂ := rfl
@[simp] lemma binary_cofan.mk_inl {P : C} (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) :
  (binary_cofan.mk ι₁ ι₂).inl = ι₁ := rfl
@[simp] lemma binary_cofan.mk_inr {P : C} (ι₁ : X ⟶ P) (ι₂ : Y ⟶ P) :
  (binary_cofan.mk ι₁ ι₂).inr = ι₂ := rfl

/-- Every `binary_fan` is isomorphic to an application of `binary_fan.mk`. -/
def iso_binary_fan_mk {X Y : C} (c : binary_fan X Y) : c ≅ binary_fan.mk c.fst c.snd :=
cones.ext (iso.refl _) (λ j, by discrete_cases; cases j; tidy)

/-- Every `binary_fan` is isomorphic to an application of `binary_fan.mk`. -/
def iso_binary_cofan_mk {X Y : C} (c : binary_cofan X Y) : c ≅ binary_cofan.mk c.inl c.inr :=
cocones.ext (iso.refl _) (λ j, by discrete_cases; cases j; tidy)

/--
This is a more convenient formulation to show that a `binary_fan` constructed using
`binary_fan.mk` is a limit cone.
-/
def binary_fan.is_limit_mk {W : C} {fst : W ⟶ X} {snd : W ⟶ Y}
  (lift : Π (s : binary_fan X Y), s.X ⟶ W)
  (fac_left : ∀ (s : binary_fan X Y), lift s ≫ fst = s.fst)
  (fac_right : ∀ (s : binary_fan X Y), lift s ≫ snd = s.snd)
  (uniq : ∀ (s : binary_fan X Y) (m : s.X ⟶ W)
    (w_fst : m ≫ fst = s.fst) (w_snd : m ≫ snd = s.snd), m = lift s) :
  is_limit (binary_fan.mk fst snd) :=
{ lift := lift,
  fac' := λ s j, by { rcases j with ⟨⟨⟩⟩, exacts [fac_left s, fac_right s], },
  uniq' := λ s m w, uniq s m (w ⟨walking_pair.left⟩) (w ⟨walking_pair.right⟩) }

/--
This is a more convenient formulation to show that a `binary_cofan` constructed using
`binary_cofan.mk` is a colimit cocone.
-/
def binary_cofan.is_colimit_mk {W : C} {inl : X ⟶ W} {inr : Y ⟶ W}
  (desc : Π (s : binary_cofan X Y), W ⟶ s.X)
  (fac_left : ∀ (s : binary_cofan X Y), inl ≫ desc s = s.inl)
  (fac_right : ∀ (s : binary_cofan X Y), inr ≫ desc s = s.inr)
  (uniq : ∀ (s : binary_cofan X Y) (m : W ⟶ s.X)
    (w_inl : inl ≫ m = s.inl) (w_inr : inr ≫ m = s.inr), m = desc s) :
  is_colimit (binary_cofan.mk inl inr) :=
{ desc := desc,
  fac' := λ s j, by { rcases j with ⟨⟨⟩⟩, exacts [fac_left s, fac_right s], },
  uniq' := λ s m w, uniq s m (w ⟨walking_pair.left⟩) (w ⟨walking_pair.right⟩) }

/-- If `s` is a limit binary fan over `X` and `Y`, then every pair of morphisms `f : W ⟶ X` and
    `g : W ⟶ Y` induces a morphism `l : W ⟶ s.X` satisfying `l ≫ s.fst = f` and `l ≫ s.snd = g`.
    -/
@[simps]
def binary_fan.is_limit.lift' {W X Y : C} {s : binary_fan X Y} (h : is_limit s) (f : W ⟶ X)
  (g : W ⟶ Y) : {l : W ⟶ s.X // l ≫ s.fst = f ∧ l ≫ s.snd = g} :=
⟨h.lift $ binary_fan.mk f g, h.fac _ _, h.fac _ _⟩

/-- If `s` is a colimit binary cofan over `X` and `Y`,, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `l : s.X ⟶ W` satisfying `s.inl ≫ l = f` and `s.inr ≫ l = g`.
    -/
@[simps]
def binary_cofan.is_colimit.desc' {W X Y : C} {s : binary_cofan X Y} (h : is_colimit s) (f : X ⟶ W)
  (g : Y ⟶ W) : {l : s.X ⟶ W // s.inl ≫ l = f ∧ s.inr ≫ l = g} :=
⟨h.desc $ binary_cofan.mk f g, h.fac _ _, h.fac _ _⟩

/-- Binary products are symmetric. -/
def binary_fan.is_limit_flip {X Y : C} {c : binary_fan X Y} (hc : is_limit c) :
  is_limit (binary_fan.mk c.snd c.fst) :=
binary_fan.is_limit_mk (λ s, hc.lift (binary_fan.mk s.snd s.fst))
  (λ s, hc.fac _ _) (λ s, hc.fac _ _)
  (λ s m e₁ e₂, binary_fan.is_limit.hom_ext hc
    (e₂.trans (hc.fac (binary_fan.mk s.snd s.fst) ⟨walking_pair.left⟩).symm)
    (e₁.trans (hc.fac (binary_fan.mk s.snd s.fst) ⟨walking_pair.right⟩).symm))

lemma binary_fan.is_limit_iff_is_iso_fst {X Y : C} (h : is_terminal Y) (c : binary_fan X Y) :
  nonempty (is_limit c) ↔ is_iso c.fst :=
begin
  split,
  { rintro ⟨H⟩,
    obtain ⟨l, hl, -⟩ := binary_fan.is_limit.lift' H (𝟙 X) (h.from X),
    exact ⟨⟨l, binary_fan.is_limit.hom_ext H
      (by simpa [hl, -category.comp_id] using category.comp_id _) (h.hom_ext _ _), hl⟩⟩ },
  { introI,
    exact ⟨binary_fan.is_limit.mk _ (λ _ f _, f ≫ inv c.fst)
      (λ _ _ _, by simp) (λ _ _ _, h.hom_ext _ _)
      (λ _ _ _ _ e _, by simp [← e])⟩ }
end

lemma binary_fan.is_limit_iff_is_iso_snd {X Y : C} (h : is_terminal X) (c : binary_fan X Y) :
  nonempty (is_limit c) ↔ is_iso c.snd :=
begin
  refine iff.trans _ (binary_fan.is_limit_iff_is_iso_fst h (binary_fan.mk c.snd c.fst)),
  exact ⟨λ h, ⟨binary_fan.is_limit_flip h.some⟩,
    λ h, ⟨(binary_fan.is_limit_flip h.some).of_iso_limit (iso_binary_fan_mk c).symm⟩⟩,
end

/-- If `X' ≅ X`, then `X × Y` also is the product of `X'` and `Y`. -/
noncomputable
def binary_fan.is_limit_comp_left_iso {X Y X' : C} (c : binary_fan X Y) (f : X ⟶ X')
  [is_iso f] (h : is_limit c) : is_limit (binary_fan.mk (c.fst ≫ f) c.snd) :=
begin
  fapply binary_fan.is_limit_mk,
  { exact λ s, h.lift (binary_fan.mk (s.fst ≫ inv f) s.snd) },
  { intro s, simp },
  { intro s, simp },
  { intros s m e₁ e₂, apply binary_fan.is_limit.hom_ext h; simpa }
end

/-- If `Y' ≅ Y`, then `X x Y` also is the product of `X` and `Y'`. -/
noncomputable
def binary_fan.is_limit_comp_right_iso {X Y Y' : C} (c : binary_fan X Y) (f : Y ⟶ Y')
  [is_iso f] (h : is_limit c) : is_limit (binary_fan.mk c.fst (c.snd ≫ f)) :=
binary_fan.is_limit_flip $ binary_fan.is_limit_comp_left_iso _ f (binary_fan.is_limit_flip h)

/-- Binary coproducts are symmetric. -/
def binary_cofan.is_colimit_flip {X Y : C} {c : binary_cofan X Y} (hc : is_colimit c) :
  is_colimit (binary_cofan.mk c.inr c.inl) :=
binary_cofan.is_colimit_mk (λ s, hc.desc (binary_cofan.mk s.inr s.inl))
  (λ s, hc.fac _ _) (λ s, hc.fac _ _)
  (λ s m e₁ e₂, binary_cofan.is_colimit.hom_ext hc
    (e₂.trans (hc.fac (binary_cofan.mk s.inr s.inl) ⟨walking_pair.left⟩).symm)
    (e₁.trans (hc.fac (binary_cofan.mk s.inr s.inl) ⟨walking_pair.right⟩).symm))

lemma binary_cofan.is_colimit_iff_is_iso_inl {X Y : C} (h : is_initial Y) (c : binary_cofan X Y) :
  nonempty (is_colimit c) ↔ is_iso c.inl :=
begin
  split,
  { rintro ⟨H⟩,
    obtain ⟨l, hl, -⟩ := binary_cofan.is_colimit.desc' H (𝟙 X) (h.to X),
    exact ⟨⟨l, hl, binary_cofan.is_colimit.hom_ext H (by simp [reassoc_of hl]) (h.hom_ext _ _)⟩⟩ },
  { introI,
    exact ⟨binary_cofan.is_colimit.mk _ (λ _ f _, inv c.inl ≫ f)
      (λ _ _ _, is_iso.hom_inv_id_assoc _ _) (λ _ _ _, h.hom_ext _ _)
      (λ _ _ _ _ e _, (is_iso.eq_inv_comp _).mpr e)⟩ }
end

lemma binary_cofan.is_colimit_iff_is_iso_inr {X Y : C} (h : is_initial X) (c : binary_cofan X Y) :
  nonempty (is_colimit c) ↔ is_iso c.inr :=
begin
  refine iff.trans _ (binary_cofan.is_colimit_iff_is_iso_inl h (binary_cofan.mk c.inr c.inl)),
  exact ⟨λ h, ⟨binary_cofan.is_colimit_flip h.some⟩,
    λ h, ⟨(binary_cofan.is_colimit_flip h.some).of_iso_colimit (iso_binary_cofan_mk c).symm⟩⟩,
end

/-- If `X' ≅ X`, then `X ⨿ Y` also is the coproduct of `X'` and `Y`. -/
noncomputable
def binary_cofan.is_colimit_comp_left_iso {X Y X' : C} (c : binary_cofan X Y) (f : X' ⟶ X)
  [is_iso f] (h : is_colimit c) : is_colimit (binary_cofan.mk (f ≫ c.inl) c.inr) :=
begin
  fapply binary_cofan.is_colimit_mk,
  { exact λ s, h.desc (binary_cofan.mk (inv f ≫ s.inl) s.inr) },
  { intro s, simp },
  { intro s, simp },
  { intros s m e₁ e₂,
    apply binary_cofan.is_colimit.hom_ext h,
    { rw ← cancel_epi f, simpa using e₁ },
    { simpa } }
end

/-- If `Y' ≅ Y`, then `X ⨿ Y` also is the coproduct of `X` and `Y'`. -/
noncomputable
def binary_cofan.is_colimit_comp_right_iso {X Y Y' : C} (c : binary_cofan X Y) (f : Y' ⟶ Y)
  [is_iso f] (h : is_colimit c) : is_colimit (binary_cofan.mk c.inl (f ≫ c.inr)) :=
binary_cofan.is_colimit_flip $
  binary_cofan.is_colimit_comp_left_iso _ f (binary_cofan.is_colimit_flip h)

/-- An abbreviation for `has_limit (pair X Y)`. -/
abbreviation has_binary_product (X Y : C) := has_limit (pair X Y)
/-- An abbreviation for `has_colimit (pair X Y)`. -/
abbreviation has_binary_coproduct (X Y : C) := has_colimit (pair X Y)

/-- If we have a product of `X` and `Y`, we can access it using `prod X Y` or
    `X ⨯ Y`. -/
abbreviation prod (X Y : C) [has_binary_product X Y] := limit (pair X Y)

/-- If we have a coproduct of `X` and `Y`, we can access it using `coprod X Y ` or
    `X ⨿ Y`. -/
abbreviation coprod (X Y : C) [has_binary_coproduct X Y] := colimit (pair X Y)

notation X ` ⨯ `:20 Y:20 := prod X Y
notation X ` ⨿ `:20 Y:20 := coprod X Y

/-- The projection map to the first component of the product. -/
abbreviation prod.fst {X Y : C} [has_binary_product X Y] : X ⨯ Y ⟶ X :=
limit.π (pair X Y) ⟨walking_pair.left⟩

/-- The projecton map to the second component of the product. -/
abbreviation prod.snd {X Y : C} [has_binary_product X Y] : X ⨯ Y ⟶ Y :=
limit.π (pair X Y) ⟨walking_pair.right⟩

/-- The inclusion map from the first component of the coproduct. -/
abbreviation coprod.inl {X Y : C} [has_binary_coproduct X Y] : X ⟶ X ⨿ Y :=
colimit.ι (pair X Y) ⟨walking_pair.left⟩

/-- The inclusion map from the second component of the coproduct. -/
abbreviation coprod.inr {X Y : C} [has_binary_coproduct X Y] : Y ⟶ X ⨿ Y :=
colimit.ι (pair X Y) ⟨walking_pair.right⟩

/-- The binary fan constructed from the projection maps is a limit. -/
def prod_is_prod (X Y : C) [has_binary_product X Y] :
  is_limit (binary_fan.mk (prod.fst : X ⨯ Y ⟶ X) prod.snd) :=
(limit.is_limit _).of_iso_limit (cones.ext (iso.refl _) (by { rintro (_ | _), tidy }))

/-- The binary cofan constructed from the coprojection maps is a colimit. -/
def coprod_is_coprod (X Y : C) [has_binary_coproduct X Y] :
  is_colimit (binary_cofan.mk (coprod.inl : X ⟶ X ⨿ Y) coprod.inr) :=
(colimit.is_colimit _).of_iso_colimit (cocones.ext (iso.refl _) (by { rintro (_ | _), tidy }))

@[ext] lemma prod.hom_ext {W X Y : C} [has_binary_product X Y] {f g : W ⟶ X ⨯ Y}
  (h₁ : f ≫ prod.fst = g ≫ prod.fst) (h₂ : f ≫ prod.snd = g ≫ prod.snd) : f = g :=
binary_fan.is_limit.hom_ext (limit.is_limit _) h₁ h₂

@[ext] lemma coprod.hom_ext {W X Y : C} [has_binary_coproduct X Y] {f g : X ⨿ Y ⟶ W}
  (h₁ : coprod.inl ≫ f = coprod.inl ≫ g) (h₂ : coprod.inr ≫ f = coprod.inr ≫ g) : f = g :=
binary_cofan.is_colimit.hom_ext (colimit.is_colimit _) h₁ h₂

/-- If the product of `X` and `Y` exists, then every pair of morphisms `f : W ⟶ X` and `g : W ⟶ Y`
    induces a morphism `prod.lift f g : W ⟶ X ⨯ Y`. -/
abbreviation prod.lift {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) : W ⟶ X ⨯ Y :=
limit.lift _ (binary_fan.mk f g)

/-- diagonal arrow of the binary product in the category `fam I` -/
abbreviation diag (X : C) [has_binary_product X X] : X ⟶ X ⨯ X :=
prod.lift (𝟙 _) (𝟙 _)

/-- If the coproduct of `X` and `Y` exists, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `coprod.desc f g : X ⨿ Y ⟶ W`. -/
abbreviation coprod.desc {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) :
  X ⨿ Y ⟶ W :=
colimit.desc _ (binary_cofan.mk f g)

/-- codiagonal arrow of the binary coproduct -/
abbreviation codiag (X : C) [has_binary_coproduct X X] : X ⨿ X ⟶ X :=
coprod.desc (𝟙 _) (𝟙 _)

@[simp, reassoc]
lemma prod.lift_fst {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) :
  prod.lift f g ≫ prod.fst = f :=
limit.lift_π _ _

@[simp, reassoc]
lemma prod.lift_snd {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) :
  prod.lift f g ≫ prod.snd = g :=
limit.lift_π _ _

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.inl_desc {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) :
  coprod.inl ≫ coprod.desc f g = f :=
colimit.ι_desc _ _

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.inr_desc {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) :
  coprod.inr ≫ coprod.desc f g = g :=
colimit.ι_desc _ _

instance prod.mono_lift_of_mono_left {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y)
  [mono f] : mono (prod.lift f g) :=
mono_of_mono_fac $ prod.lift_fst _ _

instance prod.mono_lift_of_mono_right {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y)
  [mono g] : mono (prod.lift f g) :=
mono_of_mono_fac $ prod.lift_snd _ _

instance coprod.epi_desc_of_epi_left {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W)
  [epi f] : epi (coprod.desc f g) :=
epi_of_epi_fac $ coprod.inl_desc _ _

instance coprod.epi_desc_of_epi_right {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W)
  [epi g] : epi (coprod.desc f g) :=
epi_of_epi_fac $ coprod.inr_desc _ _

/-- If the product of `X` and `Y` exists, then every pair of morphisms `f : W ⟶ X` and `g : W ⟶ Y`
    induces a morphism `l : W ⟶ X ⨯ Y` satisfying `l ≫ prod.fst = f` and `l ≫ prod.snd = g`. -/
def prod.lift' {W X Y : C} [has_binary_product X Y] (f : W ⟶ X) (g : W ⟶ Y) :
  {l : W ⟶ X ⨯ Y // l ≫ prod.fst = f ∧ l ≫ prod.snd = g} :=
⟨prod.lift f g, prod.lift_fst _ _, prod.lift_snd _ _⟩

/-- If the coproduct of `X` and `Y` exists, then every pair of morphisms `f : X ⟶ W` and
    `g : Y ⟶ W` induces a morphism `l : X ⨿ Y ⟶ W` satisfying `coprod.inl ≫ l = f` and
    `coprod.inr ≫ l = g`. -/
def coprod.desc' {W X Y : C} [has_binary_coproduct X Y] (f : X ⟶ W) (g : Y ⟶ W) :
  {l : X ⨿ Y ⟶ W // coprod.inl ≫ l = f ∧ coprod.inr ≫ l = g} :=
⟨coprod.desc f g, coprod.inl_desc _ _, coprod.inr_desc _ _⟩

/-- If the products `W ⨯ X` and `Y ⨯ Z` exist, then every pair of morphisms `f : W ⟶ Y` and
    `g : X ⟶ Z` induces a morphism `prod.map f g : W ⨯ X ⟶ Y ⨯ Z`. -/
def prod.map {W X Y Z : C} [has_binary_product W X] [has_binary_product Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : W ⨯ X ⟶ Y ⨯ Z :=
lim_map (map_pair f g)

/-- If the coproducts `W ⨿ X` and `Y ⨿ Z` exist, then every pair of morphisms `f : W ⟶ Y` and
    `g : W ⟶ Z` induces a morphism `coprod.map f g : W ⨿ X ⟶ Y ⨿ Z`. -/
def coprod.map {W X Y Z : C} [has_binary_coproduct W X] [has_binary_coproduct Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : W ⨿ X ⟶ Y ⨿ Z :=
colim_map (map_pair f g)

section prod_lemmas

-- Making the reassoc version of this a simp lemma seems to be more harmful than helpful.
@[reassoc, simp]
lemma prod.comp_lift {V W X Y : C} [has_binary_product X Y] (f : V ⟶ W) (g : W ⟶ X) (h : W ⟶ Y) :
  f ≫ prod.lift g h = prod.lift (f ≫ g) (f ≫ h) :=
by { ext; simp }

lemma prod.comp_diag {X Y : C} [has_binary_product Y Y] (f : X ⟶ Y) :
  f ≫ diag Y = prod.lift f f :=
by simp

@[simp, reassoc]
lemma prod.map_fst {W X Y Z : C} [has_binary_product W X] [has_binary_product Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : prod.map f g ≫ prod.fst = prod.fst ≫ f :=
lim_map_π _ _

@[simp, reassoc]
lemma prod.map_snd {W X Y Z : C} [has_binary_product W X] [has_binary_product Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : prod.map f g ≫ prod.snd = prod.snd ≫ g :=
lim_map_π _ _

@[simp] lemma prod.map_id_id {X Y : C} [has_binary_product X Y] :
  prod.map (𝟙 X) (𝟙 Y) = 𝟙 _ :=
by { ext; simp }

@[simp] lemma prod.lift_fst_snd {X Y : C} [has_binary_product X Y] :
  prod.lift prod.fst prod.snd = 𝟙 (X ⨯ Y) :=
by { ext; simp }

@[simp, reassoc] lemma prod.lift_map {V W X Y Z : C} [has_binary_product W X]
  [has_binary_product Y Z] (f : V ⟶ W) (g : V ⟶ X) (h : W ⟶ Y) (k : X ⟶ Z) :
  prod.lift f g ≫ prod.map h k = prod.lift (f ≫ h) (g ≫ k) :=
by { ext; simp }

@[simp] lemma prod.lift_fst_comp_snd_comp {W X Y Z : C} [has_binary_product W Y]
  [has_binary_product X Z] (g : W ⟶ X) (g' : Y ⟶ Z) :
  prod.lift (prod.fst ≫ g) (prod.snd ≫ g') = prod.map g g' :=
by { rw ← prod.lift_map, simp }

-- We take the right hand side here to be simp normal form, as this way composition lemmas for
-- `f ≫ h` and `g ≫ k` can fire (eg `id_comp`) , while `map_fst` and `map_snd` can still work just
-- as well.
@[simp, reassoc]
lemma prod.map_map {A₁ A₂ A₃ B₁ B₂ B₃ : C}
  [has_binary_product A₁ B₁] [has_binary_product A₂ B₂] [has_binary_product A₃ B₃]
  (f : A₁ ⟶ A₂) (g : B₁ ⟶ B₂) (h : A₂ ⟶ A₃) (k : B₂ ⟶ B₃) :
  prod.map f g ≫ prod.map h k = prod.map (f ≫ h) (g ≫ k) :=
by { ext; simp }

-- TODO: is it necessary to weaken the assumption here?
@[reassoc]
lemma prod.map_swap {A B X Y : C} (f : A ⟶ B) (g : X ⟶ Y)
  [has_limits_of_shape (discrete walking_pair) C] :
  prod.map (𝟙 X) f ≫ prod.map g (𝟙 B) = prod.map g (𝟙 A) ≫ prod.map (𝟙 Y) f :=
by simp

@[reassoc] lemma prod.map_comp_id {X Y Z W : C} (f : X ⟶ Y) (g : Y ⟶ Z)
  [has_binary_product X W] [has_binary_product Z W] [has_binary_product Y W] :
  prod.map (f ≫ g) (𝟙 W) = prod.map f (𝟙 W) ≫ prod.map g (𝟙 W) :=
by simp

@[reassoc] lemma prod.map_id_comp {X Y Z W : C} (f : X ⟶ Y) (g : Y ⟶ Z)
  [has_binary_product W X] [has_binary_product W Y] [has_binary_product W Z] :
  prod.map (𝟙 W) (f ≫ g) = prod.map (𝟙 W) f ≫ prod.map (𝟙 W) g :=
by simp

/-- If the products `W ⨯ X` and `Y ⨯ Z` exist, then every pair of isomorphisms `f : W ≅ Y` and
    `g : X ≅ Z` induces an isomorphism `prod.map_iso f g : W ⨯ X ≅ Y ⨯ Z`. -/
@[simps]
def prod.map_iso {W X Y Z : C} [has_binary_product W X] [has_binary_product Y Z]
  (f : W ≅ Y) (g : X ≅ Z) : W ⨯ X ≅ Y ⨯ Z :=
{ hom := prod.map f.hom g.hom,
  inv := prod.map f.inv g.inv }

instance is_iso_prod {W X Y Z : C} [has_binary_product W X] [has_binary_product Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) [is_iso f] [is_iso g] : is_iso (prod.map f g) :=
is_iso.of_iso (prod.map_iso (as_iso f) (as_iso g))

instance prod.map_mono {C : Type*} [category C] {W X Y Z : C} (f : W ⟶ Y) (g : X ⟶ Z) [mono f]
  [mono g] [has_binary_product W X] [has_binary_product Y Z] : mono (prod.map f g) :=
⟨λ A i₁ i₂ h, begin
  ext,
  { rw ← cancel_mono f, simpa using congr_arg (λ f, f ≫ prod.fst) h },
  { rw ← cancel_mono g, simpa using congr_arg (λ f, f ≫ prod.snd) h }
end⟩

@[simp, reassoc]
lemma prod.diag_map {X Y : C} (f : X ⟶ Y) [has_binary_product X X] [has_binary_product Y Y] :
  diag X ≫ prod.map f f = f ≫ diag Y :=
by simp

@[simp, reassoc]
lemma prod.diag_map_fst_snd {X Y : C} [has_binary_product X Y]
  [has_binary_product (X ⨯ Y) (X ⨯ Y)] :
  diag (X ⨯ Y) ≫ prod.map prod.fst prod.snd = 𝟙 (X ⨯ Y) :=
by simp

@[simp, reassoc]
lemma prod.diag_map_fst_snd_comp  [has_limits_of_shape (discrete walking_pair) C]
  {X X' Y Y' : C} (g : X ⟶ Y) (g' : X' ⟶ Y') :
  diag (X ⨯ X') ≫ prod.map (prod.fst ≫ g) (prod.snd ≫ g') = prod.map g g' :=
by simp

instance {X : C} [has_binary_product X X] : is_split_mono (diag X) :=
is_split_mono.mk' { retraction := prod.fst }

end prod_lemmas

section coprod_lemmas

@[simp, reassoc]
lemma coprod.desc_comp {V W X Y : C} [has_binary_coproduct X Y] (f : V ⟶ W) (g : X ⟶ V)
  (h : Y ⟶ V) :
  coprod.desc g h ≫ f = coprod.desc (g ≫ f) (h ≫ f) :=
by { ext; simp }

lemma coprod.diag_comp {X Y : C} [has_binary_coproduct X X] (f : X ⟶ Y) :
  codiag X ≫ f = coprod.desc f f :=
by simp

@[simp, reassoc]
lemma coprod.inl_map {W X Y Z : C} [has_binary_coproduct W X] [has_binary_coproduct Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : coprod.inl ≫ coprod.map f g = f ≫ coprod.inl :=
ι_colim_map _ _

@[simp, reassoc]
lemma coprod.inr_map {W X Y Z : C} [has_binary_coproduct W X] [has_binary_coproduct Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) : coprod.inr ≫ coprod.map f g = g ≫ coprod.inr :=
ι_colim_map _ _

@[simp]
lemma coprod.map_id_id {X Y : C} [has_binary_coproduct X Y] :
  coprod.map (𝟙 X) (𝟙 Y) = 𝟙 _ :=
by { ext; simp }

@[simp]
lemma coprod.desc_inl_inr {X Y : C} [has_binary_coproduct X Y] :
  coprod.desc coprod.inl coprod.inr = 𝟙 (X ⨿ Y) :=
by { ext; simp }

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.map_desc {S T U V W : C} [has_binary_coproduct U W] [has_binary_coproduct T V]
  (f : U ⟶ S) (g : W ⟶ S) (h : T ⟶ U) (k : V ⟶ W) :
  coprod.map h k ≫ coprod.desc f g = coprod.desc (h ≫ f) (k ≫ g) :=
by { ext; simp }

@[simp]
lemma coprod.desc_comp_inl_comp_inr {W X Y Z : C}
  [has_binary_coproduct W Y] [has_binary_coproduct X Z]
  (g : W ⟶ X) (g' : Y ⟶ Z) :
  coprod.desc (g ≫ coprod.inl) (g' ≫ coprod.inr) = coprod.map g g' :=
by { rw ← coprod.map_desc, simp }

-- We take the right hand side here to be simp normal form, as this way composition lemmas for
-- `f ≫ h` and `g ≫ k` can fire (eg `id_comp`) , while `inl_map` and `inr_map` can still work just
-- as well.
@[simp, reassoc]
lemma coprod.map_map {A₁ A₂ A₃ B₁ B₂ B₃ : C}
  [has_binary_coproduct A₁ B₁] [has_binary_coproduct A₂ B₂] [has_binary_coproduct A₃ B₃]
  (f : A₁ ⟶ A₂) (g : B₁ ⟶ B₂) (h : A₂ ⟶ A₃) (k : B₂ ⟶ B₃) :
  coprod.map f g ≫ coprod.map h k = coprod.map (f ≫ h) (g ≫ k) :=
by { ext; simp }

-- I don't think it's a good idea to make any of the following three simp lemmas.
@[reassoc]
lemma coprod.map_swap {A B X Y : C} (f : A ⟶ B) (g : X ⟶ Y)
  [has_colimits_of_shape (discrete walking_pair) C] :
  coprod.map (𝟙 X) f ≫ coprod.map g (𝟙 B) = coprod.map g (𝟙 A) ≫ coprod.map (𝟙 Y) f :=
by simp

@[reassoc] lemma coprod.map_comp_id {X Y Z W : C} (f : X ⟶ Y) (g : Y ⟶ Z)
  [has_binary_coproduct Z W] [has_binary_coproduct Y W] [has_binary_coproduct X W] :
  coprod.map (f ≫ g) (𝟙 W) = coprod.map f (𝟙 W) ≫ coprod.map g (𝟙 W) :=
by simp

@[reassoc] lemma coprod.map_id_comp {X Y Z W : C} (f : X ⟶ Y) (g : Y ⟶ Z)
  [has_binary_coproduct W X] [has_binary_coproduct W Y] [has_binary_coproduct W Z] :
  coprod.map (𝟙 W) (f ≫ g) = coprod.map (𝟙 W) f ≫ coprod.map (𝟙 W) g :=
by simp

/-- If the coproducts `W ⨿ X` and `Y ⨿ Z` exist, then every pair of isomorphisms `f : W ≅ Y` and
    `g : W ≅ Z` induces a isomorphism `coprod.map_iso f g : W ⨿ X ≅ Y ⨿ Z`. -/
@[simps]
def coprod.map_iso {W X Y Z : C} [has_binary_coproduct W X] [has_binary_coproduct Y Z]
  (f : W ≅ Y) (g : X ≅ Z) : W ⨿ X ≅ Y ⨿ Z :=
{ hom := coprod.map f.hom g.hom,
  inv := coprod.map f.inv g.inv }

instance is_iso_coprod {W X Y Z : C} [has_binary_coproduct W X] [has_binary_coproduct Y Z]
  (f : W ⟶ Y) (g : X ⟶ Z) [is_iso f] [is_iso g] : is_iso (coprod.map f g) :=
is_iso.of_iso (coprod.map_iso (as_iso f) (as_iso g))

instance coprod.map_epi {C : Type*} [category C] {W X Y Z : C} (f : W ⟶ Y) (g : X ⟶ Z) [epi f]
  [epi g] [has_binary_coproduct W X] [has_binary_coproduct Y Z] : epi (coprod.map f g) :=
⟨λ A i₁ i₂ h, begin
  ext,
  { rw ← cancel_epi f, simpa using congr_arg (λ f, coprod.inl ≫ f) h },
  { rw ← cancel_epi g, simpa using congr_arg (λ f, coprod.inr ≫ f) h }
end⟩

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.map_codiag {X Y : C} (f : X ⟶ Y) [has_binary_coproduct X X]
  [has_binary_coproduct Y Y] :
  coprod.map f f ≫ codiag Y = codiag X ≫ f :=
by simp

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.map_inl_inr_codiag {X Y : C} [has_binary_coproduct X Y]
  [has_binary_coproduct (X ⨿ Y) (X ⨿ Y)] :
  coprod.map coprod.inl coprod.inr ≫ codiag (X ⨿ Y) = 𝟙 (X ⨿ Y) :=
by simp

-- The simp linter says simp can prove the reassoc version of this lemma.
@[reassoc, simp]
lemma coprod.map_comp_inl_inr_codiag [has_colimits_of_shape (discrete walking_pair) C]
  {X X' Y Y' : C} (g : X ⟶ Y) (g' : X' ⟶ Y') :
  coprod.map (g ≫ coprod.inl) (g' ≫ coprod.inr) ≫ codiag (Y ⨿ Y') = coprod.map g g' :=
by simp

end coprod_lemmas

variables (C)

/--
`has_binary_products` represents a choice of product for every pair of objects.

See <https://stacks.math.columbia.edu/tag/001T>.
-/
abbreviation has_binary_products := has_limits_of_shape (discrete walking_pair) C

/--
`has_binary_coproducts` represents a choice of coproduct for every pair of objects.

See <https://stacks.math.columbia.edu/tag/04AP>.
-/
abbreviation has_binary_coproducts := has_colimits_of_shape (discrete walking_pair) C

/-- If `C` has all limits of diagrams `pair X Y`, then it has all binary products -/
lemma has_binary_products_of_has_limit_pair [Π {X Y : C}, has_limit (pair X Y)] :
  has_binary_products C :=
{ has_limit := λ F, has_limit_of_iso (diagram_iso_pair F).symm }

/-- If `C` has all colimits of diagrams `pair X Y`, then it has all binary coproducts -/
lemma has_binary_coproducts_of_has_colimit_pair [Π {X Y : C}, has_colimit (pair X Y)] :
  has_binary_coproducts C :=
{ has_colimit := λ F, has_colimit_of_iso (diagram_iso_pair F) }

section
variables {C}

/-- The braiding isomorphism which swaps a binary product. -/
@[simps] def prod.braiding (P Q : C) [has_binary_product P Q] [has_binary_product Q P] :
  P ⨯ Q ≅ Q ⨯ P :=
{ hom := prod.lift prod.snd prod.fst,
  inv := prod.lift prod.snd prod.fst }

/-- The braiding isomorphism can be passed through a map by swapping the order. -/
@[reassoc] lemma braid_natural [has_binary_products C] {W X Y Z : C} (f : X ⟶ Y) (g : Z ⟶ W) :
  prod.map f g ≫ (prod.braiding _ _).hom = (prod.braiding _ _).hom ≫ prod.map g f :=
by simp

@[reassoc] lemma prod.symmetry' (P Q : C) [has_binary_product P Q] [has_binary_product Q P] :
  prod.lift prod.snd prod.fst ≫ prod.lift prod.snd prod.fst = 𝟙 (P ⨯ Q) :=
(prod.braiding _ _).hom_inv_id

/-- The braiding isomorphism is symmetric. -/
@[reassoc] lemma prod.symmetry (P Q : C) [has_binary_product P Q] [has_binary_product Q P] :
  (prod.braiding P Q).hom ≫ (prod.braiding Q P).hom = 𝟙 _ :=
(prod.braiding _ _).hom_inv_id

/-- The associator isomorphism for binary products. -/
@[simps] def prod.associator [has_binary_products C] (P Q R : C) :
  (P ⨯ Q) ⨯ R ≅ P ⨯ (Q ⨯ R) :=
{ hom :=
  prod.lift
    (prod.fst ≫ prod.fst)
    (prod.lift (prod.fst ≫ prod.snd) prod.snd),
  inv :=
  prod.lift
    (prod.lift prod.fst (prod.snd ≫ prod.fst))
    (prod.snd ≫ prod.snd) }

@[reassoc]
lemma prod.pentagon [has_binary_products C] (W X Y Z : C) :
  prod.map ((prod.associator W X Y).hom) (𝟙 Z) ≫
      (prod.associator W (X ⨯ Y) Z).hom ≫ prod.map (𝟙 W) ((prod.associator X Y Z).hom) =
    (prod.associator (W ⨯ X) Y Z).hom ≫ (prod.associator W X (Y ⨯ Z)).hom :=
by simp

@[reassoc]
lemma prod.associator_naturality [has_binary_products C] {X₁ X₂ X₃ Y₁ Y₂ Y₃ : C}
  (f₁ : X₁ ⟶ Y₁) (f₂ : X₂ ⟶ Y₂) (f₃ : X₃ ⟶ Y₃) :
  prod.map (prod.map f₁ f₂) f₃ ≫ (prod.associator Y₁ Y₂ Y₃).hom =
    (prod.associator X₁ X₂ X₃).hom ≫ prod.map f₁ (prod.map f₂ f₃) :=
by simp

variables [has_terminal C]

/-- The left unitor isomorphism for binary products with the terminal object. -/
@[simps] def prod.left_unitor (P : C) [has_binary_product (⊤_ C) P] :
  ⊤_ C ⨯ P ≅ P :=
{ hom := prod.snd,
  inv := prod.lift (terminal.from P) (𝟙 _) }

/-- The right unitor isomorphism for binary products with the terminal object. -/
@[simps] def prod.right_unitor (P : C) [has_binary_product P (⊤_ C)] :
  P ⨯ ⊤_ C ≅ P :=
{ hom := prod.fst,
  inv := prod.lift (𝟙 _) (terminal.from P) }

@[reassoc]
lemma prod.left_unitor_hom_naturality [has_binary_products C] (f : X ⟶ Y) :
  prod.map (𝟙 _) f ≫ (prod.left_unitor Y).hom = (prod.left_unitor X).hom ≫ f :=
prod.map_snd _ _

@[reassoc]
lemma prod.left_unitor_inv_naturality [has_binary_products C] (f : X ⟶ Y) :
  (prod.left_unitor X).inv ≫ prod.map (𝟙 _) f = f ≫ (prod.left_unitor Y).inv :=
by rw [iso.inv_comp_eq, ← category.assoc, iso.eq_comp_inv, prod.left_unitor_hom_naturality]

@[reassoc]
lemma prod.right_unitor_hom_naturality [has_binary_products C] (f : X ⟶ Y) :
  prod.map f (𝟙 _) ≫ (prod.right_unitor Y).hom = (prod.right_unitor X).hom ≫ f :=
prod.map_fst _ _

@[reassoc]
lemma prod_right_unitor_inv_naturality [has_binary_products C] (f : X ⟶ Y) :
  (prod.right_unitor X).inv ≫ prod.map f (𝟙 _) = f ≫ (prod.right_unitor Y).inv :=
by rw [iso.inv_comp_eq, ← category.assoc, iso.eq_comp_inv, prod.right_unitor_hom_naturality]

lemma prod.triangle [has_binary_products C] (X Y : C) :
  (prod.associator X (⊤_ C) Y).hom ≫ prod.map (𝟙 X) ((prod.left_unitor Y).hom) =
    prod.map ((prod.right_unitor X).hom) (𝟙 Y) :=
by tidy

end

section

variables {C} [has_binary_coproducts C]

/-- The braiding isomorphism which swaps a binary coproduct. -/
@[simps] def coprod.braiding (P Q : C) : P ⨿ Q ≅ Q ⨿ P :=
{ hom := coprod.desc coprod.inr coprod.inl,
  inv := coprod.desc coprod.inr coprod.inl }

@[reassoc] lemma coprod.symmetry' (P Q : C) :
  coprod.desc coprod.inr coprod.inl ≫ coprod.desc coprod.inr coprod.inl = 𝟙 (P ⨿ Q) :=
(coprod.braiding _ _).hom_inv_id

/-- The braiding isomorphism is symmetric. -/
lemma coprod.symmetry (P Q : C) :
  (coprod.braiding P Q).hom ≫ (coprod.braiding Q P).hom = 𝟙 _ :=
coprod.symmetry' _ _

/-- The associator isomorphism for binary coproducts. -/
@[simps] def coprod.associator
  (P Q R : C) : (P ⨿ Q) ⨿ R ≅ P ⨿ (Q ⨿ R) :=
{ hom :=
  coprod.desc
    (coprod.desc coprod.inl (coprod.inl ≫ coprod.inr))
    (coprod.inr ≫ coprod.inr),
  inv :=
  coprod.desc
    (coprod.inl ≫ coprod.inl)
    (coprod.desc (coprod.inr ≫ coprod.inl) coprod.inr) }

lemma coprod.pentagon (W X Y Z : C) :
  coprod.map ((coprod.associator W X Y).hom) (𝟙 Z) ≫
      (coprod.associator W (X ⨿ Y) Z).hom ≫ coprod.map (𝟙 W) ((coprod.associator X Y Z).hom) =
    (coprod.associator (W ⨿ X) Y Z).hom ≫ (coprod.associator W X (Y ⨿ Z)).hom :=
by simp

lemma coprod.associator_naturality {X₁ X₂ X₃ Y₁ Y₂ Y₃ : C} (f₁ : X₁ ⟶ Y₁) (f₂ : X₂ ⟶ Y₂)
  (f₃ : X₃ ⟶ Y₃) :
  coprod.map (coprod.map f₁ f₂) f₃ ≫ (coprod.associator Y₁ Y₂ Y₃).hom =
    (coprod.associator X₁ X₂ X₃).hom ≫ coprod.map f₁ (coprod.map f₂ f₃) :=
by simp

variables [has_initial C]

/-- The left unitor isomorphism for binary coproducts with the initial object. -/
@[simps] def coprod.left_unitor
  (P : C) : ⊥_ C ⨿ P ≅ P :=
{ hom := coprod.desc (initial.to P) (𝟙 _),
  inv := coprod.inr }

/-- The right unitor isomorphism for binary coproducts with the initial object. -/
@[simps] def coprod.right_unitor
  (P : C) : P ⨿ ⊥_ C ≅ P :=
{ hom := coprod.desc (𝟙 _) (initial.to P),
  inv := coprod.inl }

lemma coprod.triangle (X Y : C) :
  (coprod.associator X (⊥_ C) Y).hom ≫ coprod.map (𝟙 X) ((coprod.left_unitor Y).hom) =
    coprod.map ((coprod.right_unitor X).hom) (𝟙 Y) :=
by tidy

end

section prod_functor
variables {C} [has_binary_products C]

/-- The binary product functor. -/
@[simps]
def prod.functor : C ⥤ C ⥤ C :=
{ obj := λ X, { obj := λ Y, X ⨯ Y, map := λ Y Z, prod.map (𝟙 X) },
  map := λ Y Z f, { app := λ T, prod.map f (𝟙 T) }}

/-- The product functor can be decomposed. -/
def prod.functor_left_comp (X Y : C) :
  prod.functor.obj (X ⨯ Y) ≅ prod.functor.obj Y ⋙ prod.functor.obj X :=
nat_iso.of_components (prod.associator _ _) (by tidy)

end prod_functor

section coprod_functor
variables {C} [has_binary_coproducts C]

/-- The binary coproduct functor. -/
@[simps]
def coprod.functor : C ⥤ C ⥤ C :=
{ obj := λ X, { obj := λ Y, X ⨿ Y, map := λ Y Z, coprod.map (𝟙 X) },
  map := λ Y Z f, { app := λ T, coprod.map f (𝟙 T) }}

/-- The coproduct functor can be decomposed. -/
def coprod.functor_left_comp (X Y : C) :
  coprod.functor.obj (X ⨿ Y) ≅ coprod.functor.obj Y ⋙ coprod.functor.obj X :=
nat_iso.of_components (coprod.associator _ _) (by tidy)

end coprod_functor

section prod_comparison

universe w

variables {C} {D : Type u₂} [category.{w} D]
variables (F : C ⥤ D) {A A' B B' : C}
variables [has_binary_product A B] [has_binary_product A' B']
variables [has_binary_product (F.obj A) (F.obj B)] [has_binary_product (F.obj A') (F.obj B')]
/--
The product comparison morphism.

In `category_theory/limits/preserves` we show this is always an iso iff F preserves binary products.
-/
def prod_comparison (F : C ⥤ D) (A B : C)
  [has_binary_product A B] [has_binary_product (F.obj A) (F.obj B)] :
  F.obj (A ⨯ B) ⟶ F.obj A ⨯ F.obj B :=
prod.lift (F.map prod.fst) (F.map prod.snd)

@[simp, reassoc]
lemma prod_comparison_fst :
  prod_comparison F A B ≫ prod.fst = F.map prod.fst :=
prod.lift_fst _ _

@[simp, reassoc]
lemma prod_comparison_snd :
  prod_comparison F A B ≫ prod.snd = F.map prod.snd :=
prod.lift_snd _ _

/-- Naturality of the prod_comparison morphism in both arguments. -/
@[reassoc] lemma prod_comparison_natural (f : A ⟶ A') (g : B ⟶ B') :
  F.map (prod.map f g) ≫ prod_comparison F A' B' =
    prod_comparison F A B ≫ prod.map (F.map f) (F.map g) :=
begin
  rw [prod_comparison, prod_comparison, prod.lift_map, ← F.map_comp, ← F.map_comp,
      prod.comp_lift, ← F.map_comp, prod.map_fst, ← F.map_comp, prod.map_snd]
end

/--
The product comparison morphism from `F(A ⨯ -)` to `FA ⨯ F-`, whose components are given by
`prod_comparison`.
-/
@[simps]
def prod_comparison_nat_trans [has_binary_products C] [has_binary_products D]
  (F : C ⥤ D) (A : C) :
  prod.functor.obj A ⋙ F ⟶ F ⋙ prod.functor.obj (F.obj A) :=
{ app := λ B, prod_comparison F A B,
  naturality' := λ B B' f, by simp [prod_comparison_natural] }

@[reassoc]
lemma inv_prod_comparison_map_fst [is_iso (prod_comparison F A B)] :
  inv (prod_comparison F A B) ≫ F.map prod.fst = prod.fst :=
by simp [is_iso.inv_comp_eq]

@[reassoc]
lemma inv_prod_comparison_map_snd [is_iso (prod_comparison F A B)] :
  inv (prod_comparison F A B) ≫ F.map prod.snd = prod.snd :=
by simp [is_iso.inv_comp_eq]

/-- If the product comparison morphism is an iso, its inverse is natural. -/
@[reassoc]
lemma prod_comparison_inv_natural (f : A ⟶ A') (g : B ⟶ B')
  [is_iso (prod_comparison F A B)] [is_iso (prod_comparison F A' B')] :
  inv (prod_comparison F A B) ≫ F.map (prod.map f g) =
    prod.map (F.map f) (F.map g) ≫ inv (prod_comparison F A' B') :=
by rw [is_iso.eq_comp_inv, category.assoc, is_iso.inv_comp_eq, prod_comparison_natural]

/--
The natural isomorphism `F(A ⨯ -) ≅ FA ⨯ F-`, provided each `prod_comparison F A B` is an
isomorphism (as `B` changes).
-/
@[simps {rhs_md := semireducible}]
def prod_comparison_nat_iso [has_binary_products C] [has_binary_products D]
  (A : C) [∀ B, is_iso (prod_comparison F A B)] :
  prod.functor.obj A ⋙ F ≅ F ⋙ prod.functor.obj (F.obj A) :=
{ hom := prod_comparison_nat_trans F A
  ..(@as_iso _ _ _ _ _ (nat_iso.is_iso_of_is_iso_app ⟨_, _⟩)) }

end prod_comparison

section coprod_comparison

universe w

variables {C} {D : Type u₂} [category.{w} D]
variables (F : C ⥤ D) {A A' B B' : C}
variables [has_binary_coproduct A B] [has_binary_coproduct A' B']
variables [has_binary_coproduct (F.obj A) (F.obj B)] [has_binary_coproduct (F.obj A') (F.obj B')]
/--
The coproduct comparison morphism.

In `category_theory/limits/preserves` we show
this is always an iso iff F preserves binary coproducts.
-/
def coprod_comparison (F : C ⥤ D) (A B : C)
  [has_binary_coproduct A B] [has_binary_coproduct (F.obj A) (F.obj B)] :
  F.obj A ⨿ F.obj B ⟶ F.obj (A ⨿ B) :=
coprod.desc (F.map coprod.inl) (F.map coprod.inr)

@[simp, reassoc]
lemma coprod_comparison_inl :
  coprod.inl ≫ coprod_comparison F A B  = F.map coprod.inl :=
coprod.inl_desc _ _

@[simp, reassoc]

/-- Naturality of the coprod_comparison morphism in both arguments. -/
@[reassoc] lemma coprod_comparison_natural (f : A ⟶ A') (g : B ⟶ B') :
  coprod_comparison F A B ≫ F.map (coprod.map f g) =
    coprod.map (F.map f) (F.map g) ≫ coprod_comparison F A' B' :=
begin
  rw [coprod_comparison, coprod_comparison, coprod.map_desc, ← F.map_comp, ← F.map_comp,
      coprod.desc_comp, ← F.map_comp, coprod.inl_map, ← F.map_comp, coprod.inr_map]
end

/--
The coproduct comparison morphism from `FA ⨿ F-` to `F(A ⨿ -)`, whose components are given by
`coprod_comparison`.
-/
@[simps]
def coprod_comparison_nat_trans [has_binary_coproducts C] [has_binary_coproducts D]
  (F : C ⥤ D) (A : C) :
  F ⋙ coprod.functor.obj (F.obj A) ⟶ coprod.functor.obj A ⋙ F :=
{ app := λ B, coprod_comparison F A B,
  naturality' := λ B B' f, by simp [coprod_comparison_natural] }

@[reassoc]
lemma map_inl_inv_coprod_comparison [is_iso (coprod_comparison F A B)] :
  F.map coprod.inl ≫ inv (coprod_comparison F A B) = coprod.inl :=
by simp [is_iso.inv_comp_eq]

@[reassoc]
lemma map_inr_inv_coprod_comparison [is_iso (coprod_comparison F A B)] :
  F.map coprod.inr ≫ inv (coprod_comparison F A B) = coprod.inr :=
by simp [is_iso.inv_comp_eq]

/-- If the coproduct comparison morphism is an iso, its inverse is natural. -/
@[reassoc]
lemma coprod_comparison_inv_natural (f : A ⟶ A') (g : B ⟶ B')
  [is_iso (coprod_comparison F A B)] [is_iso (coprod_comparison F A' B')] :
  inv (coprod_comparison F A B) ≫ coprod.map (F.map f) (F.map g) =
    F.map (coprod.map f g) ≫ inv (coprod_comparison F A' B') :=
by rw [is_iso.eq_comp_inv, category.assoc, is_iso.inv_comp_eq, coprod_comparison_natural]

/--
The natural isomorphism `FA ⨿ F- ≅ F(A ⨿ -)`, provided each `coprod_comparison F A B` is an
isomorphism (as `B` changes).
-/
@[simps {rhs_md := semireducible}]
def coprod_comparison_nat_iso [has_binary_coproducts C] [has_binary_coproducts D]
  (A : C) [∀ B, is_iso (coprod_comparison F A B)] :
  F ⋙ coprod.functor.obj (F.obj A) ≅ coprod.functor.obj A ⋙ F :=
{ hom := coprod_comparison_nat_trans F A
  ..(@as_iso _ _ _ _ _ (nat_iso.is_iso_of_is_iso_app ⟨_, _⟩)) }

end coprod_comparison

end category_theory.limits

open category_theory.limits

namespace category_theory

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

/-- Auxiliary definition for `over.coprod`. -/
@[simps]
def over.coprod_obj [has_binary_coproducts C] {A : C} : over A → over A ⥤ over A := λ f,
{ obj := λ g, over.mk (coprod.desc f.hom g.hom),
  map := λ g₁ g₂ k, over.hom_mk (coprod.map (𝟙 _) k.left) }

/-- A category with binary coproducts has a functorial `sup` operation on over categories. -/
@[simps]
def over.coprod [has_binary_coproducts C] {A : C} : over A ⥤ over A ⥤ over A :=
{ obj := λ f, over.coprod_obj f,
  map := λ f₁ f₂ k,
  { app := λ g, over.hom_mk (coprod.map k.left (𝟙 _))
      (by { dsimp, rw [coprod.map_desc, category.id_comp, over.w k] }),
    naturality' := λ f g k, by ext; { dsimp, simp, }, },
  map_id' := λ X, by ext; { dsimp, simp, },
  map_comp' := λ X Y Z f g, by ext; { dsimp, simp, }, }.

end category_theory


/-
Copyright (c) 2022 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg
-/

import Aesop

@[aesop [10% cases, safe constructors]]
inductive Even : Nat → Prop
  | zero : Even 0
  | plus_two : Even n → Even (n + 2)

example : Even 2 := by
  aesop

-- Removing the Aesop attribute erases all rules associated with the identifier
-- from all rule sets.
attribute [-aesop] Even

example : Even 2 := by
  fail_if_success aesop (options := { terminal := true })
  aesop (add safe Even)

-- We can also selectively remove rules in a certain phase or with a certain
-- builder.
attribute [aesop [unsafe 10% cases, safe constructors]] Even

erase_aesop_rules [ unsafe Even ]

example : Even 2 := by
  aesop

erase_aesop_rules [ constructors Even ]

example : Even 2 := by
  fail_if_success aesop (options := { terminal := true })
  aesop (add safe constructors Even)


/-
Copyright (c) 2022 James Gallicchio.

Authors: James Gallicchio
-/

import LeanColls.List.Basic
import LeanColls.FoldableCorrect

open LeanColls

namespace List

@[simp]
theorem canonicalToList_eq_id (l : List τ)
  : canonicalToList l.foldl = l
  := by
  simp [canonicalToList, foldl]
  suffices ∀ acc, foldl (fun acc x => acc ++ [x]) acc l = acc ++ l by
    have := this []
    simp at this
    exact this
  induction l with
  | nil =>
    simp [foldl]
  | cons x xs ih =>
    intro acc
    simp [foldl]
    rw [ih]
    simp [List.append_assoc]

instance instFoldable'Correct : Foldable'.Correct (List τ) τ inferInstance where
  fold l := l.foldl
  foldCorrect := by
    simp [Foldable.fold, canonicalToList_eq_id]
  fold' := foldl'
  memCorrect := by
    simp [Foldable.fold, canonicalToList_eq_id]
  fold'Correct := by
    intro β c f acc
    simp [Foldable.fold]
    simp
    suffices ∀ l (_ : c = l) (h' : ∀ {x}, x ∈ l → x ∈ c), foldl' c f acc = foldl' l (fun acc x h => f acc x (h' h)) acc from
      this (canonicalToList c.foldl) (by rw [canonicalToList_eq_id]) (by
        rw [canonicalToList_eq_id]
        intros; trivial
        )
    intro l h_l h'
    cases h_l
    apply congrArg
    rfl

@[simp]
theorem canonicalToList_of_foldable (l : List τ)
  : canonicalToList (Foldable.fold l) = l
  := by simp [Foldable.fold]

instance : Iterable (List τ) τ where
  ρ := List τ
  step := List.front?
  toIterator := id

instance : Enumerable (List τ) τ where
  ρ := List τ
  fromEnumerator := id
  insert := λ
    | none => []
    | some ⟨x,xs⟩ => x::xs


/-
Copyright (c) 2022 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel

! This file was ported from Lean 3 source module category_theory.generator
! leanprover-community/mathlib commit f187f1074fa1857c94589cc653c786cadc4c35ff
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.CategoryTheory.Balanced
import Mathbin.CategoryTheory.Limits.EssentiallySmall
import Mathbin.CategoryTheory.Limits.Opposites
import Mathbin.CategoryTheory.Limits.Shapes.ZeroMorphisms
import Mathbin.CategoryTheory.Subobject.Lattice
import Mathbin.CategoryTheory.Subobject.WellPowered
import Mathbin.Data.Set.Opposite

/-!
# Separating and detecting sets

There are several non-equivalent notions of a generator of a category. Here, we consider two of
them:

* We say that `𝒢` is a separating set if the functors `C(G, -)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `h ≫ f = h ≫ g` for all `h` with domain in `𝒢` implies `f = g`.
* We say that `𝒢` is a detecting set if the functors `C(G, -)` collectively reflect isomorphisms,
    i.e., if any `h` with domain in `𝒢` uniquely factors through `f`, then `f` is an isomorphism.

There are, of course, also the dual notions of coseparating and codetecting sets.

## Main results

We
* define predicates `is_separating`, `is_coseparating`, `is_detecting` and `is_codetecting` on
  sets of objects;
* show that separating and coseparating are dual notions;
* show that detecting and codetecting are dual notions;
* show that if `C` has equalizers, then detecting implies separating;
* show that if `C` has coequalizers, then codetecting implies separating;
* show that if `C` is balanced, then separating implies detecting and coseparating implies
  codetecting;
* show that `∅` is separating if and only if `∅` is coseparating if and only if `C` is thin;
* show that `∅` is detecting if and only if `∅` is codetecting if and only if `C` is a groupoid;
* define predicates `is_separator`, `is_coseparator`, `is_detector` and `is_codetector` as the
  singleton counterparts to the definitions for sets above and restate the above results in this
  situation;
* show that `G` is a separator if and only if `coyoneda.obj (op G)` is faithful (and the dual);
* show that `G` is a detector if and only if `coyoneda.obj (op G)` reflects isomorphisms (and the
  dual).

## Future work

* We currently don't have any examples yet.
* We will want typeclasses `has_separator C` and similar.

-/

universe w v₁ v₂ u₁ u₂

open CategoryTheory.Limits Opposite

namespace CategoryTheory

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

/-- We say that `𝒢` is a separating set if the functors `C(G, -)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `h ≫ f = h ≫ g` for all `h` with domain in `𝒢` implies `f = g`. -/
def IsSeparating (𝒢 : Set C) : Prop :=
  ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ G ∈ 𝒢, ∀ (h : G ⟶ X), h ≫ f = h ≫ g) → f = g
#align category_theory.is_separating CategoryTheory.IsSeparating

/-- We say that `𝒢` is a coseparating set if the functors `C(-, G)` for `G ∈ 𝒢` are collectively
    faithful, i.e., if `f ≫ h = g ≫ h` for all `h` with codomain in `𝒢` implies `f = g`. -/
def IsCoseparating (𝒢 : Set C) : Prop :=
  ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ G ∈ 𝒢, ∀ (h : Y ⟶ G), f ≫ h = g ≫ h) → f = g
#align category_theory.is_coseparating CategoryTheory.IsCoseparating

/-- We say that `𝒢` is a detecting set if the functors `C(G, -)` collectively reflect isomorphisms,
    i.e., if any `h` with domain in `𝒢` uniquely factors through `f`, then `f` is an isomorphism. -/
def IsDetecting (𝒢 : Set C) : Prop :=
  ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ G ∈ 𝒢, ∀ (h : G ⟶ Y), ∃! h' : G ⟶ X, h' ≫ f = h) → IsIso f
#align category_theory.is_detecting CategoryTheory.IsDetecting

/-- We say that `𝒢` is a codetecting set if the functors `C(-, G)` collectively reflect
    isomorphisms, i.e., if any `h` with codomain in `G` uniquely factors through `f`, then `f` is
    an isomorphism. -/
def IsCodetecting (𝒢 : Set C) : Prop :=
  ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ G ∈ 𝒢, ∀ (h : X ⟶ G), ∃! h' : Y ⟶ G, f ≫ h' = h) → IsIso f
#align category_theory.is_codetecting CategoryTheory.IsCodetecting

section Dual

theorem isSeparating_op_iff (𝒢 : Set C) : IsSeparating 𝒢.op ↔ IsCoseparating 𝒢 :=
  by
  refine' ⟨fun h𝒢 X Y f g hfg => _, fun h𝒢 X Y f g hfg => _⟩
  · refine' Quiver.Hom.op_inj (h𝒢 _ _ fun G hG h => Quiver.Hom.unop_inj _)
    simpa only [unop_comp, Quiver.Hom.unop_op] using hfg _ (Set.mem_op.1 hG) _
  · refine' Quiver.Hom.unop_inj (h𝒢 _ _ fun G hG h => Quiver.Hom.op_inj _)
    simpa only [op_comp, Quiver.Hom.op_unop] using hfg _ (Set.op_mem_op.2 hG) _
#align category_theory.is_separating_op_iff CategoryTheory.isSeparating_op_iff

theorem isCoseparating_op_iff (𝒢 : Set C) : IsCoseparating 𝒢.op ↔ IsSeparating 𝒢 :=
  by
  refine' ⟨fun h𝒢 X Y f g hfg => _, fun h𝒢 X Y f g hfg => _⟩
  · refine' Quiver.Hom.op_inj (h𝒢 _ _ fun G hG h => Quiver.Hom.unop_inj _)
    simpa only [unop_comp, Quiver.Hom.unop_op] using hfg _ (Set.mem_op.1 hG) _
  · refine' Quiver.Hom.unop_inj (h𝒢 _ _ fun G hG h => Quiver.Hom.op_inj _)
    simpa only [op_comp, Quiver.Hom.op_unop] using hfg _ (Set.op_mem_op.2 hG) _
#align category_theory.is_coseparating_op_iff CategoryTheory.isCoseparating_op_iff

theorem isCoseparating_unop_iff (𝒢 : Set Cᵒᵖ) : IsCoseparating 𝒢.unop ↔ IsSeparating 𝒢 := by
  rw [← is_separating_op_iff, Set.unop_op]
#align category_theory.is_coseparating_unop_iff CategoryTheory.isCoseparating_unop_iff

theorem isSeparating_unop_iff (𝒢 : Set Cᵒᵖ) : IsSeparating 𝒢.unop ↔ IsCoseparating 𝒢 := by
  rw [← is_coseparating_op_iff, Set.unop_op]
#align category_theory.is_separating_unop_iff CategoryTheory.isSeparating_unop_iff

theorem isDetecting_op_iff (𝒢 : Set C) : IsDetecting 𝒢.op ↔ IsCodetecting 𝒢 :=
  by
  refine' ⟨fun h𝒢 X Y f hf => _, fun h𝒢 X Y f hf => _⟩
  · refine' (is_iso_op_iff _).1 (h𝒢 _ fun G hG h => _)
    obtain ⟨t, ht, ht'⟩ := hf (unop G) (Set.mem_op.1 hG) h.unop
    exact
      ⟨t.op, Quiver.Hom.unop_inj ht, fun y hy => Quiver.Hom.unop_inj (ht' _ (Quiver.Hom.op_inj hy))⟩
  · refine' (is_iso_unop_iff _).1 (h𝒢 _ fun G hG h => _)
    obtain ⟨t, ht, ht'⟩ := hf (op G) (Set.op_mem_op.2 hG) h.op
    refine' ⟨t.unop, Quiver.Hom.op_inj ht, fun y hy => Quiver.Hom.op_inj (ht' _ _)⟩
    exact Quiver.Hom.unop_inj (by simpa only using hy)
#align category_theory.is_detecting_op_iff CategoryTheory.isDetecting_op_iff

theorem isCodetecting_op_iff (𝒢 : Set C) : IsCodetecting 𝒢.op ↔ IsDetecting 𝒢 :=
  by
  refine' ⟨fun h𝒢 X Y f hf => _, fun h𝒢 X Y f hf => _⟩
  · refine' (is_iso_op_iff _).1 (h𝒢 _ fun G hG h => _)
    obtain ⟨t, ht, ht'⟩ := hf (unop G) (Set.mem_op.1 hG) h.unop
    exact
      ⟨t.op, Quiver.Hom.unop_inj ht, fun y hy => Quiver.Hom.unop_inj (ht' _ (Quiver.Hom.op_inj hy))⟩
  · refine' (is_iso_unop_iff _).1 (h𝒢 _ fun G hG h => _)
    obtain ⟨t, ht, ht'⟩ := hf (op G) (Set.op_mem_op.2 hG) h.op
    refine' ⟨t.unop, Quiver.Hom.op_inj ht, fun y hy => Quiver.Hom.op_inj (ht' _ _)⟩
    exact Quiver.Hom.unop_inj (by simpa only using hy)
#align category_theory.is_codetecting_op_iff CategoryTheory.isCodetecting_op_iff

theorem isDetecting_unop_iff (𝒢 : Set Cᵒᵖ) : IsDetecting 𝒢.unop ↔ IsCodetecting 𝒢 := by
  rw [← is_codetecting_op_iff, Set.unop_op]
#align category_theory.is_detecting_unop_iff CategoryTheory.isDetecting_unop_iff

theorem isCodetecting_unop_iff {𝒢 : Set Cᵒᵖ} : IsCodetecting 𝒢.unop ↔ IsDetecting 𝒢 := by
  rw [← is_detecting_op_iff, Set.unop_op]
#align category_theory.is_codetecting_unop_iff CategoryTheory.isCodetecting_unop_iff

end Dual

theorem IsDetecting.isSeparating [HasEqualizers C] {𝒢 : Set C} (h𝒢 : IsDetecting 𝒢) :
    IsSeparating 𝒢 := fun X Y f g hfg =>
  have : IsIso (equalizer.ι f g) := h𝒢 _ fun G hG h => equalizer.existsUnique _ (hfg _ hG _)
  eq_of_epi_equalizer
#align category_theory.is_detecting.is_separating CategoryTheory.IsDetecting.isSeparating

section

theorem IsCodetecting.isCoseparating [HasCoequalizers C] {𝒢 : Set C} :
    IsCodetecting 𝒢 → IsCoseparating 𝒢 := by
  simpa only [← is_separating_op_iff, ← is_detecting_op_iff] using is_detecting.is_separating
#align category_theory.is_codetecting.is_coseparating CategoryTheory.IsCodetecting.isCoseparating

end

theorem IsSeparating.isDetecting [Balanced C] {𝒢 : Set C} (h𝒢 : IsSeparating 𝒢) : IsDetecting 𝒢 :=
  by
  intro X Y f hf
  refine'
    (is_iso_iff_mono_and_epi _).2 ⟨⟨fun Z g h hgh => h𝒢 _ _ fun G hG i => _⟩, ⟨fun Z g h hgh => _⟩⟩
  · obtain ⟨t, -, ht⟩ := hf G hG (i ≫ g ≫ f)
    rw [ht (i ≫ g) (category.assoc _ _ _), ht (i ≫ h) (hgh.symm ▸ category.assoc _ _ _)]
  · refine' h𝒢 _ _ fun G hG i => _
    obtain ⟨t, rfl, -⟩ := hf G hG i
    rw [category.assoc, hgh, category.assoc]
#align category_theory.is_separating.is_detecting CategoryTheory.IsSeparating.isDetecting

section

attribute [local instance] balanced_opposite

theorem IsCoseparating.isCodetecting [Balanced C] {𝒢 : Set C} :
    IsCoseparating 𝒢 → IsCodetecting 𝒢 := by
  simpa only [← is_detecting_op_iff, ← is_separating_op_iff] using is_separating.is_detecting
#align category_theory.is_coseparating.is_codetecting CategoryTheory.IsCoseparating.isCodetecting

end

theorem isDetecting_iff_isSeparating [HasEqualizers C] [Balanced C] (𝒢 : Set C) :
    IsDetecting 𝒢 ↔ IsSeparating 𝒢 :=
  ⟨IsDetecting.isSeparating, IsSeparating.isDetecting⟩
#align category_theory.is_detecting_iff_is_separating CategoryTheory.isDetecting_iff_isSeparating

theorem isCodetecting_iff_isCoseparating [HasCoequalizers C] [Balanced C] {𝒢 : Set C} :
    IsCodetecting 𝒢 ↔ IsCoseparating 𝒢 :=
  ⟨IsCodetecting.isCoseparating, IsCoseparating.isCodetecting⟩
#align category_theory.is_codetecting_iff_is_coseparating CategoryTheory.isCodetecting_iff_isCoseparating

section Mono

theorem IsSeparating.mono {𝒢 : Set C} (h𝒢 : IsSeparating 𝒢) {ℋ : Set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
    IsSeparating ℋ := fun X Y f g hfg => h𝒢 _ _ fun G hG h => hfg _ (h𝒢ℋ hG) _
#align category_theory.is_separating.mono CategoryTheory.IsSeparating.mono

theorem IsCoseparating.mono {𝒢 : Set C} (h𝒢 : IsCoseparating 𝒢) {ℋ : Set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
    IsCoseparating ℋ := fun X Y f g hfg => h𝒢 _ _ fun G hG h => hfg _ (h𝒢ℋ hG) _
#align category_theory.is_coseparating.mono CategoryTheory.IsCoseparating.mono

theorem IsDetecting.mono {𝒢 : Set C} (h𝒢 : IsDetecting 𝒢) {ℋ : Set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
    IsDetecting ℋ := fun X Y f hf => h𝒢 _ fun G hG h => hf _ (h𝒢ℋ hG) _
#align category_theory.is_detecting.mono CategoryTheory.IsDetecting.mono

theorem IsCodetecting.mono {𝒢 : Set C} (h𝒢 : IsCodetecting 𝒢) {ℋ : Set C} (h𝒢ℋ : 𝒢 ⊆ ℋ) :
    IsCodetecting ℋ := fun X Y f hf => h𝒢 _ fun G hG h => hf _ (h𝒢ℋ hG) _
#align category_theory.is_codetecting.mono CategoryTheory.IsCodetecting.mono

end Mono

section Empty

theorem thin_of_isSeparating_empty (h : IsSeparating (∅ : Set C)) : Quiver.IsThin C := fun _ _ =>
  ⟨fun f g => h _ _ fun G => False.elim⟩
#align category_theory.thin_of_is_separating_empty CategoryTheory.thin_of_isSeparating_empty

theorem isSeparating_empty_of_thin [Quiver.IsThin C] : IsSeparating (∅ : Set C) :=
  fun X Y f g hfg => Subsingleton.elim _ _
#align category_theory.is_separating_empty_of_thin CategoryTheory.isSeparating_empty_of_thin

theorem thin_of_isCoseparating_empty (h : IsCoseparating (∅ : Set C)) : Quiver.IsThin C :=
  fun _ _ => ⟨fun f g => h _ _ fun G => False.elim⟩
#align category_theory.thin_of_is_coseparating_empty CategoryTheory.thin_of_isCoseparating_empty

theorem isCoseparating_empty_of_thin [Quiver.IsThin C] : IsCoseparating (∅ : Set C) :=
  fun X Y f g hfg => Subsingleton.elim _ _
#align category_theory.is_coseparating_empty_of_thin CategoryTheory.isCoseparating_empty_of_thin

theorem groupoid_of_isDetecting_empty (h : IsDetecting (∅ : Set C)) {X Y : C} (f : X ⟶ Y) :
    IsIso f :=
  h _ fun G => False.elim
#align category_theory.groupoid_of_is_detecting_empty CategoryTheory.groupoid_of_isDetecting_empty

theorem isDetecting_empty_of_groupoid [∀ {X Y : C} (f : X ⟶ Y), IsIso f] :
    IsDetecting (∅ : Set C) := fun X Y f hf => inferInstance
#align category_theory.is_detecting_empty_of_groupoid CategoryTheory.isDetecting_empty_of_groupoid

theorem groupoid_of_isCodetecting_empty (h : IsCodetecting (∅ : Set C)) {X Y : C} (f : X ⟶ Y) :
    IsIso f :=
  h _ fun G => False.elim
#align category_theory.groupoid_of_is_codetecting_empty CategoryTheory.groupoid_of_isCodetecting_empty

theorem isCodetecting_empty_of_groupoid [∀ {X Y : C} (f : X ⟶ Y), IsIso f] :
    IsCodetecting (∅ : Set C) := fun X Y f hf => inferInstance
#align category_theory.is_codetecting_empty_of_groupoid CategoryTheory.isCodetecting_empty_of_groupoid

end Empty

theorem isSeparating_iff_epi (𝒢 : Set C)
    [∀ A : C, HasCoproduct fun f : ΣG : 𝒢, (G : C) ⟶ A => (f.1 : C)] :
    IsSeparating 𝒢 ↔ ∀ A : C, Epi (Sigma.desc (@Sigma.snd 𝒢 fun G => (G : C) ⟶ A)) :=
  by
  refine' ⟨fun h A => ⟨fun Z u v huv => h _ _ fun G hG f => _⟩, fun h X Y f g hh => _⟩
  · simpa using sigma.ι (fun f : ΣG : 𝒢, (G : C) ⟶ A => (f.1 : C)) ⟨⟨G, hG⟩, f⟩ ≫= huv
  · haveI := h X
    refine'
      (cancel_epi (sigma.desc (@Sigma.snd 𝒢 fun G => (G : C) ⟶ X))).1 (colimit.hom_ext fun j => _)
    simpa using hh j.as.1.1 j.as.1.2 j.as.2
#align category_theory.is_separating_iff_epi CategoryTheory.isSeparating_iff_epi

theorem isCoseparating_iff_mono (𝒢 : Set C)
    [∀ A : C, HasProduct fun f : ΣG : 𝒢, A ⟶ (G : C) => (f.1 : C)] :
    IsCoseparating 𝒢 ↔ ∀ A : C, Mono (Pi.lift (@Sigma.snd 𝒢 fun G => A ⟶ (G : C))) :=
  by
  refine' ⟨fun h A => ⟨fun Z u v huv => h _ _ fun G hG f => _⟩, fun h X Y f g hh => _⟩
  · simpa using huv =≫ pi.π (fun f : ΣG : 𝒢, A ⟶ (G : C) => (f.1 : C)) ⟨⟨G, hG⟩, f⟩
  · haveI := h Y
    refine' (cancel_mono (pi.lift (@Sigma.snd 𝒢 fun G => Y ⟶ (G : C)))).1 (limit.hom_ext fun j => _)
    simpa using hh j.as.1.1 j.as.1.2 j.as.2
#align category_theory.is_coseparating_iff_mono CategoryTheory.isCoseparating_iff_mono

/-- An ingredient of the proof of the Special Adjoint Functor Theorem: a complete well-powered
    category with a small coseparating set has an initial object.

    In fact, it follows from the Special Adjoint Functor Theorem that `C` is already cocomplete,
    see `has_colimits_of_has_limits_of_is_coseparating`. -/
theorem hasInitial_of_isCoseparating [WellPowered C] [HasLimits C] {𝒢 : Set C} [Small.{v₁} 𝒢]
    (h𝒢 : IsCoseparating 𝒢) : HasInitial C :=
  by
  haveI := has_products_of_shape_of_small C 𝒢
  haveI := fun A => hasProductsOfShape_of_small.{v₁} C (ΣG : 𝒢, A ⟶ (G : C))
  letI := completeLatticeOfCompleteSemilatticeInf (subobject (pi_obj (coe : 𝒢 → C)))
  suffices ∀ A : C, Unique (((⊥ : subobject (pi_obj (coe : 𝒢 → C))) : C) ⟶ A) by
    exact has_initial_of_unique ((⊥ : subobject (pi_obj (coe : 𝒢 → C))) : C)
  refine' fun A => ⟨⟨_⟩, fun f => _⟩
  · let s := pi.lift fun f : ΣG : 𝒢, A ⟶ (G : C) => id (pi.π (coe : 𝒢 → C)) f.1
    let t := pi.lift (@Sigma.snd 𝒢 fun G => A ⟶ (G : C))
    haveI : mono t := (is_coseparating_iff_mono 𝒢).1 h𝒢 A
    exact subobject.of_le_mk _ (pullback.fst : pullback s t ⟶ _) bot_le ≫ pullback.snd
  · generalize default = g
    suffices is_split_epi (equalizer.ι f g) by exact eq_of_epi_equalizer
    exact
      is_split_epi.mk'
        ⟨subobject.of_le_mk _ (equalizer.ι f g ≫ subobject.arrow _) bot_le,
          by
          ext
          simp⟩
#align category_theory.has_initial_of_is_coseparating CategoryTheory.hasInitial_of_isCoseparating

/-- An ingredient of the proof of the Special Adjoint Functor Theorem: a cocomplete well-copowered
    category with a small separating set has a terminal object.

    In fact, it follows from the Special Adjoint Functor Theorem that `C` is already complete, see
    `has_limits_of_has_colimits_of_is_separating`. -/
theorem hasTerminal_of_isSeparating [WellPowered Cᵒᵖ] [HasColimits C] {𝒢 : Set C} [Small.{v₁} 𝒢]
    (h𝒢 : IsSeparating 𝒢) : HasTerminal C :=
  by
  haveI : Small.{v₁} 𝒢.op := small_of_injective (Set.opEquiv_self 𝒢).Injective
  haveI : has_initial Cᵒᵖ := has_initial_of_is_coseparating ((is_coseparating_op_iff _).2 h𝒢)
  exact has_terminal_of_has_initial_op
#align category_theory.has_terminal_of_is_separating CategoryTheory.hasTerminal_of_isSeparating

section WellPowered

namespace Subobject

theorem eq_of_le_of_isDetecting {𝒢 : Set C} (h𝒢 : IsDetecting 𝒢) {X : C} (P Q : Subobject X)
    (h₁ : P ≤ Q) (h₂ : ∀ G ∈ 𝒢, ∀ {f : G ⟶ X}, Q.Factors f → P.Factors f) : P = Q :=
  by
  suffices is_iso (of_le _ _ h₁) by exact le_antisymm h₁ (le_of_comm (inv (of_le _ _ h₁)) (by simp))
  refine' h𝒢 _ fun G hG f => _
  have : P.factors (f ≫ Q.arrow) := h₂ _ hG ((factors_iff _ _).2 ⟨_, rfl⟩)
  refine' ⟨factor_thru _ _ this, _, fun g (hg : g ≫ _ = f) => _⟩
  · simp only [← cancel_mono Q.arrow, category.assoc, of_le_arrow, factor_thru_arrow]
  ·
    simp only [← cancel_mono (subobject.of_le _ _ h₁), ← cancel_mono Q.arrow, hg, category.assoc,
      of_le_arrow, factor_thru_arrow]
#align category_theory.subobject.eq_of_le_of_is_detecting CategoryTheory.Subobject.eq_of_le_of_isDetecting

theorem inf_eq_of_isDetecting [HasPullbacks C] {𝒢 : Set C} (h𝒢 : IsDetecting 𝒢) {X : C}
    (P Q : Subobject X) (h : ∀ G ∈ 𝒢, ∀ {f : G ⟶ X}, P.Factors f → Q.Factors f) : P ⊓ Q = P :=
  eq_of_le_of_isDetecting h𝒢 _ _ inf_le_left fun G hG f hf => (inf_factors _).2 ⟨hf, h _ hG hf⟩
#align category_theory.subobject.inf_eq_of_is_detecting CategoryTheory.Subobject.inf_eq_of_isDetecting

theorem eq_of_isDetecting [HasPullbacks C] {𝒢 : Set C} (h𝒢 : IsDetecting 𝒢) {X : C}
    (P Q : Subobject X) (h : ∀ G ∈ 𝒢, ∀ {f : G ⟶ X}, P.Factors f ↔ Q.Factors f) : P = Q :=
  calc
    P = P ⊓ Q := Eq.symm <| inf_eq_of_isDetecting h𝒢 _ _ fun G hG f hf => (h G hG).1 hf
    _ = Q ⊓ P := inf_comm
    _ = Q := inf_eq_of_isDetecting h𝒢 _ _ fun G hG f hf => (h G hG).2 hf

#align category_theory.subobject.eq_of_is_detecting CategoryTheory.Subobject.eq_of_isDetecting

end Subobject

/-- A category with pullbacks and a small detecting set is well-powered. -/
theorem wellPowered_of_isDetecting [HasPullbacks C] {𝒢 : Set C} [Small.{v₁} 𝒢]
    (h𝒢 : IsDetecting 𝒢) : WellPowered C :=
  ⟨fun X =>
    @small_of_injective _ _ _ (fun P : Subobject X => { f : ΣG : 𝒢, G.1 ⟶ X | P.Factors f.2 })
      fun P Q h => Subobject.eq_of_isDetecting h𝒢 _ _ (by simpa [Set.ext_iff] using h)⟩
#align category_theory.well_powered_of_is_detecting CategoryTheory.wellPowered_of_isDetecting

end WellPowered

namespace StructuredArrow

variable (S : D) (T : C ⥤ D)

theorem isCoseparating_proj_preimage {𝒢 : Set C} (h𝒢 : IsCoseparating 𝒢) :
    IsCoseparating ((proj S T).obj ⁻¹' 𝒢) :=
  by
  refine' fun X Y f g hfg => ext _ _ (h𝒢 _ _ fun G hG h => _)
  exact congr_arg comma_morphism.right (hfg (mk (Y.hom ≫ T.map h)) hG (hom_mk h rfl))
#align category_theory.structured_arrow.is_coseparating_proj_preimage CategoryTheory.StructuredArrow.isCoseparating_proj_preimage

end StructuredArrow

namespace CostructuredArrow

variable (S : C ⥤ D) (T : D)

theorem isSeparating_proj_preimage {𝒢 : Set C} (h𝒢 : IsSeparating 𝒢) :
    IsSeparating ((proj S T).obj ⁻¹' 𝒢) :=
  by
  refine' fun X Y f g hfg => ext _ _ (h𝒢 _ _ fun G hG h => _)
  convert congr_arg comma_morphism.left (hfg (mk (S.map h ≫ X.hom)) hG (hom_mk h rfl))
#align category_theory.costructured_arrow.is_separating_proj_preimage CategoryTheory.CostructuredArrow.isSeparating_proj_preimage

end CostructuredArrow

/-- We say that `G` is a separator if the functor `C(G, -)` is faithful. -/
def IsSeparator (G : C) : Prop :=
  IsSeparating ({G} : Set C)
#align category_theory.is_separator CategoryTheory.IsSeparator

/-- We say that `G` is a coseparator if the functor `C(-, G)` is faithful. -/
def IsCoseparator (G : C) : Prop :=
  IsCoseparating ({G} : Set C)
#align category_theory.is_coseparator CategoryTheory.IsCoseparator

/-- We say that `G` is a detector if the functor `C(G, -)` reflects isomorphisms. -/
def IsDetector (G : C) : Prop :=
  IsDetecting ({G} : Set C)
#align category_theory.is_detector CategoryTheory.IsDetector

/-- We say that `G` is a codetector if the functor `C(-, G)` reflects isomorphisms. -/
def IsCodetector (G : C) : Prop :=
  IsCodetecting ({G} : Set C)
#align category_theory.is_codetector CategoryTheory.IsCodetector

section Dual

theorem isSeparator_op_iff (G : C) : IsSeparator (op G) ↔ IsCoseparator G := by
  rw [is_separator, is_coseparator, ← is_separating_op_iff, Set.singleton_op]
#align category_theory.is_separator_op_iff CategoryTheory.isSeparator_op_iff

theorem isCoseparator_op_iff (G : C) : IsCoseparator (op G) ↔ IsSeparator G := by
  rw [is_separator, is_coseparator, ← is_coseparating_op_iff, Set.singleton_op]
#align category_theory.is_coseparator_op_iff CategoryTheory.isCoseparator_op_iff

theorem isCoseparator_unop_iff (G : Cᵒᵖ) : IsCoseparator (unop G) ↔ IsSeparator G := by
  rw [is_separator, is_coseparator, ← is_coseparating_unop_iff, Set.singleton_unop]
#align category_theory.is_coseparator_unop_iff CategoryTheory.isCoseparator_unop_iff

theorem isSeparator_unop_iff (G : Cᵒᵖ) : IsSeparator (unop G) ↔ IsCoseparator G := by
  rw [is_separator, is_coseparator, ← is_separating_unop_iff, Set.singleton_unop]
#align category_theory.is_separator_unop_iff CategoryTheory.isSeparator_unop_iff

theorem isDetector_op_iff (G : C) : IsDetector (op G) ↔ IsCodetector G := by
  rw [is_detector, is_codetector, ← is_detecting_op_iff, Set.singleton_op]
#align category_theory.is_detector_op_iff CategoryTheory.isDetector_op_iff

theorem isCodetector_op_iff (G : C) : IsCodetector (op G) ↔ IsDetector G := by
  rw [is_detector, is_codetector, ← is_codetecting_op_iff, Set.singleton_op]
#align category_theory.is_codetector_op_iff CategoryTheory.isCodetector_op_iff

theorem isCodetector_unop_iff (G : Cᵒᵖ) : IsCodetector (unop G) ↔ IsDetector G := by
  rw [is_detector, is_codetector, ← is_codetecting_unop_iff, Set.singleton_unop]
#align category_theory.is_codetector_unop_iff CategoryTheory.isCodetector_unop_iff

theorem isDetector_unop_iff (G : Cᵒᵖ) : IsDetector (unop G) ↔ IsCodetector G := by
  rw [is_detector, is_codetector, ← is_detecting_unop_iff, Set.singleton_unop]
#align category_theory.is_detector_unop_iff CategoryTheory.isDetector_unop_iff

end Dual

theorem IsDetector.isSeparator [HasEqualizers C] {G : C} : IsDetector G → IsSeparator G :=
  IsDetecting.isSeparating
#align category_theory.is_detector.is_separator CategoryTheory.IsDetector.isSeparator

theorem IsCodetector.isCoseparator [HasCoequalizers C] {G : C} : IsCodetector G → IsCoseparator G :=
  IsCodetecting.isCoseparating
#align category_theory.is_codetector.is_coseparator CategoryTheory.IsCodetector.isCoseparator

theorem IsSeparator.isDetector [Balanced C] {G : C} : IsSeparator G → IsDetector G :=
  IsSeparating.isDetecting
#align category_theory.is_separator.is_detector CategoryTheory.IsSeparator.isDetector

theorem IsCospearator.isCodetector [Balanced C] {G : C} : IsCoseparator G → IsCodetector G :=
  IsCoseparating.isCodetecting
#align category_theory.is_cospearator.is_codetector CategoryTheory.IsCospearator.isCodetector

theorem isSeparator_def (G : C) :
    IsSeparator G ↔ ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : G ⟶ X, h ≫ f = h ≫ g) → f = g :=
  ⟨fun hG X Y f g hfg =>
    hG _ _ fun H hH h => by
      obtain rfl := Set.mem_singleton_iff.1 hH
      exact hfg h,
    fun hG X Y f g hfg => hG _ _ fun h => hfg _ (Set.mem_singleton _) _⟩
#align category_theory.is_separator_def CategoryTheory.isSeparator_def

theorem IsSeparator.def {G : C} :
    IsSeparator G → ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : G ⟶ X, h ≫ f = h ≫ g) → f = g :=
  (isSeparator_def _).1
#align category_theory.is_separator.def CategoryTheory.IsSeparator.def

theorem isCoseparator_def (G : C) :
    IsCoseparator G ↔ ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : Y ⟶ G, f ≫ h = g ≫ h) → f = g :=
  ⟨fun hG X Y f g hfg =>
    hG _ _ fun H hH h => by
      obtain rfl := Set.mem_singleton_iff.1 hH
      exact hfg h,
    fun hG X Y f g hfg => hG _ _ fun h => hfg _ (Set.mem_singleton _) _⟩
#align category_theory.is_coseparator_def CategoryTheory.isCoseparator_def

theorem IsCoseparator.def {G : C} :
    IsCoseparator G → ∀ ⦃X Y : C⦄ (f g : X ⟶ Y), (∀ h : Y ⟶ G, f ≫ h = g ≫ h) → f = g :=
  (isCoseparator_def _).1
#align category_theory.is_coseparator.def CategoryTheory.IsCoseparator.def

theorem isDetector_def (G : C) :
    IsDetector G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : G ⟶ Y, ∃! h', h' ≫ f = h) → IsIso f :=
  ⟨fun hG X Y f hf =>
    hG _ fun H hH h => by
      obtain rfl := Set.mem_singleton_iff.1 hH
      exact hf h,
    fun hG X Y f hf => hG _ fun h => hf _ (Set.mem_singleton _) _⟩
#align category_theory.is_detector_def CategoryTheory.isDetector_def

theorem IsDetector.def {G : C} :
    IsDetector G → ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : G ⟶ Y, ∃! h', h' ≫ f = h) → IsIso f :=
  (isDetector_def _).1
#align category_theory.is_detector.def CategoryTheory.IsDetector.def

theorem isCodetector_def (G : C) :
    IsCodetector G ↔ ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : X ⟶ G, ∃! h', f ≫ h' = h) → IsIso f :=
  ⟨fun hG X Y f hf =>
    hG _ fun H hH h => by
      obtain rfl := Set.mem_singleton_iff.1 hH
      exact hf h,
    fun hG X Y f hf => hG _ fun h => hf _ (Set.mem_singleton _) _⟩
#align category_theory.is_codetector_def CategoryTheory.isCodetector_def

theorem IsCodetector.def {G : C} :
    IsCodetector G → ∀ ⦃X Y : C⦄ (f : X ⟶ Y), (∀ h : X ⟶ G, ∃! h', f ≫ h' = h) → IsIso f :=
  (isCodetector_def _).1
#align category_theory.is_codetector.def CategoryTheory.IsCodetector.def

theorem isSeparator_iff_faithful_coyoneda_obj (G : C) :
    IsSeparator G ↔ Faithful (coyoneda.obj (op G)) :=
  ⟨fun hG => ⟨fun X Y f g hfg => hG.def _ _ (congr_fun hfg)⟩, fun h =>
    (isSeparator_def _).2 fun X Y f g hfg => (coyoneda.obj (op G)).map_injective (funext hfg)⟩
#align category_theory.is_separator_iff_faithful_coyoneda_obj CategoryTheory.isSeparator_iff_faithful_coyoneda_obj

theorem isCoseparator_iff_faithful_yoneda_obj (G : C) : IsCoseparator G ↔ Faithful (yoneda.obj G) :=
  ⟨fun hG => ⟨fun X Y f g hfg => Quiver.Hom.unop_inj (hG.def _ _ (congr_fun hfg))⟩, fun h =>
    (isCoseparator_def _).2 fun X Y f g hfg =>
      Quiver.Hom.op_inj <| (yoneda.obj G).map_injective (funext hfg)⟩
#align category_theory.is_coseparator_iff_faithful_yoneda_obj CategoryTheory.isCoseparator_iff_faithful_yoneda_obj

theorem isSeparator_iff_epi (G : C) [∀ A : C, HasCoproduct fun f : G ⟶ A => G] :
    IsSeparator G ↔ ∀ A : C, Epi (Sigma.desc fun f : G ⟶ A => f) :=
  by
  rw [is_separator_def]
  refine' ⟨fun h A => ⟨fun Z u v huv => h _ _ fun i => _⟩, fun h X Y f g hh => _⟩
  · simpa using sigma.ι _ i ≫= huv
  · haveI := h X
    refine' (cancel_epi (sigma.desc fun f : G ⟶ X => f)).1 (colimit.hom_ext fun j => _)
    simpa using hh j.as
#align category_theory.is_separator_iff_epi CategoryTheory.isSeparator_iff_epi

theorem isCoseparator_iff_mono (G : C) [∀ A : C, HasProduct fun f : A ⟶ G => G] :
    IsCoseparator G ↔ ∀ A : C, Mono (Pi.lift fun f : A ⟶ G => f) :=
  by
  rw [is_coseparator_def]
  refine' ⟨fun h A => ⟨fun Z u v huv => h _ _ fun i => _⟩, fun h X Y f g hh => _⟩
  · simpa using huv =≫ pi.π _ i
  · haveI := h Y
    refine' (cancel_mono (pi.lift fun f : Y ⟶ G => f)).1 (limit.hom_ext fun j => _)
    simpa using hh j.as
#align category_theory.is_coseparator_iff_mono CategoryTheory.isCoseparator_iff_mono

section ZeroMorphisms

variable [HasZeroMorphisms C]

theorem isSeparator_coprod (G H : C) [HasBinaryCoproduct G H] :
    IsSeparator (G ⨿ H) ↔ IsSeparating ({G, H} : Set C) :=
  by
  refine'
    ⟨fun h X Y u v huv => _, fun h =>
      (is_separator_def _).2 fun X Y u v huv => h _ _ fun Z hZ g => _⟩
  · refine' h.def _ _ fun g => coprod.hom_ext _ _
    · simpa using huv G (by simp) (coprod.inl ≫ g)
    · simpa using huv H (by simp) (coprod.inr ≫ g)
  · simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at hZ
    rcases hZ with (rfl | rfl)
    · simpa using coprod.inl ≫= huv (coprod.desc g 0)
    · simpa using coprod.inr ≫= huv (coprod.desc 0 g)
#align category_theory.is_separator_coprod CategoryTheory.isSeparator_coprod

theorem isSeparator_coprod_of_isSeparator_left (G H : C) [HasBinaryCoproduct G H]
    (hG : IsSeparator G) : IsSeparator (G ⨿ H) :=
  (isSeparator_coprod _ _).2 <| IsSeparating.mono hG <| by simp
#align category_theory.is_separator_coprod_of_is_separator_left CategoryTheory.isSeparator_coprod_of_isSeparator_left

theorem isSeparator_coprod_of_isSeparator_right (G H : C) [HasBinaryCoproduct G H]
    (hH : IsSeparator H) : IsSeparator (G ⨿ H) :=
  (isSeparator_coprod _ _).2 <| IsSeparating.mono hH <| by simp
#align category_theory.is_separator_coprod_of_is_separator_right CategoryTheory.isSeparator_coprod_of_isSeparator_right

theorem isSeparator_sigma {β : Type w} (f : β → C) [HasCoproduct f] :
    IsSeparator (∐ f) ↔ IsSeparating (Set.range f) :=
  by
  refine'
    ⟨fun h X Y u v huv => _, fun h =>
      (is_separator_def _).2 fun X Y u v huv => h _ _ fun Z hZ g => _⟩
  · refine' h.def _ _ fun g => colimit.hom_ext fun b => _
    simpa using huv (f b.as) (by simp) (colimit.ι (discrete.functor f) _ ≫ g)
  · obtain ⟨b, rfl⟩ := Set.mem_range.1 hZ
    classical simpa using sigma.ι f b ≫= huv (sigma.desc (Pi.single b g))
#align category_theory.is_separator_sigma CategoryTheory.isSeparator_sigma

theorem isSeparator_sigma_of_isSeparator {β : Type w} (f : β → C) [HasCoproduct f] (b : β)
    (hb : IsSeparator (f b)) : IsSeparator (∐ f) :=
  (isSeparator_sigma _).2 <| IsSeparating.mono hb <| by simp
#align category_theory.is_separator_sigma_of_is_separator CategoryTheory.isSeparator_sigma_of_isSeparator

theorem isCoseparator_prod (G H : C) [HasBinaryProduct G H] :
    IsCoseparator (G ⨯ H) ↔ IsCoseparating ({G, H} : Set C) :=
  by
  refine'
    ⟨fun h X Y u v huv => _, fun h =>
      (is_coseparator_def _).2 fun X Y u v huv => h _ _ fun Z hZ g => _⟩
  · refine' h.def _ _ fun g => prod.hom_ext _ _
    · simpa using huv G (by simp) (g ≫ limits.prod.fst)
    · simpa using huv H (by simp) (g ≫ limits.prod.snd)
  · simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at hZ
    rcases hZ with (rfl | rfl)
    · simpa using huv (prod.lift g 0) =≫ limits.prod.fst
    · simpa using huv (prod.lift 0 g) =≫ limits.prod.snd
#align category_theory.is_coseparator_prod CategoryTheory.isCoseparator_prod

theorem isCoseparator_prod_of_isCoseparator_left (G H : C) [HasBinaryProduct G H]
    (hG : IsCoseparator G) : IsCoseparator (G ⨯ H) :=
  (isCoseparator_prod _ _).2 <| IsCoseparating.mono hG <| by simp
#align category_theory.is_coseparator_prod_of_is_coseparator_left CategoryTheory.isCoseparator_prod_of_isCoseparator_left

theorem isCoseparator_prod_of_isCoseparator_right (G H : C) [HasBinaryProduct G H]
    (hH : IsCoseparator H) : IsCoseparator (G ⨯ H) :=
  (isCoseparator_prod _ _).2 <| IsCoseparating.mono hH <| by simp
#align category_theory.is_coseparator_prod_of_is_coseparator_right CategoryTheory.isCoseparator_prod_of_isCoseparator_right

theorem isCoseparator_pi {β : Type w} (f : β → C) [HasProduct f] :
    IsCoseparator (∏ f) ↔ IsCoseparating (Set.range f) :=
  by
  refine'
    ⟨fun h X Y u v huv => _, fun h =>
      (is_coseparator_def _).2 fun X Y u v huv => h _ _ fun Z hZ g => _⟩
  · refine' h.def _ _ fun g => limit.hom_ext fun b => _
    simpa using huv (f b.as) (by simp) (g ≫ limit.π (discrete.functor f) _)
  · obtain ⟨b, rfl⟩ := Set.mem_range.1 hZ
    classical simpa using huv (pi.lift (Pi.single b g)) =≫ pi.π f b
#align category_theory.is_coseparator_pi CategoryTheory.isCoseparator_pi

theorem isCoseparator_pi_of_isCoseparator {β : Type w} (f : β → C) [HasProduct f] (b : β)
    (hb : IsCoseparator (f b)) : IsCoseparator (∏ f) :=
  (isCoseparator_pi _).2 <| IsCoseparating.mono hb <| by simp
#align category_theory.is_coseparator_pi_of_is_coseparator CategoryTheory.isCoseparator_pi_of_isCoseparator

end ZeroMorphisms

theorem isDetector_iff_reflectsIsomorphisms_coyoneda_obj (G : C) :
    IsDetector G ↔ ReflectsIsomorphisms (coyoneda.obj (op G)) :=
  by
  refine'
    ⟨fun hG => ⟨fun X Y f hf => hG.def _ fun h => _⟩, fun h =>
      (is_detector_def _).2 fun X Y f hf => _⟩
  · rw [is_iso_iff_bijective, Function.bijective_iff_existsUnique] at hf
    exact hf h
  · suffices is_iso ((coyoneda.obj (op G)).map f) by
      exact @is_iso_of_reflects_iso _ _ _ _ _ _ _ (coyoneda.obj (op G)) _ h
    rwa [is_iso_iff_bijective, Function.bijective_iff_existsUnique]
#align category_theory.is_detector_iff_reflects_isomorphisms_coyoneda_obj CategoryTheory.isDetector_iff_reflectsIsomorphisms_coyoneda_obj

theorem isCodetector_iff_reflectsIsomorphisms_yoneda_obj (G : C) :
    IsCodetector G ↔ ReflectsIsomorphisms (yoneda.obj G) :=
  by
  refine' ⟨fun hG => ⟨fun X Y f hf => _⟩, fun h => (is_codetector_def _).2 fun X Y f hf => _⟩
  · refine' (is_iso_unop_iff _).1 (hG.def _ _)
    rwa [is_iso_iff_bijective, Function.bijective_iff_existsUnique] at hf
  · rw [← is_iso_op_iff]
    suffices is_iso ((yoneda.obj G).map f.op) by
      exact @is_iso_of_reflects_iso _ _ _ _ _ _ _ (yoneda.obj G) _ h
    rwa [is_iso_iff_bijective, Function.bijective_iff_existsUnique]
#align category_theory.is_codetector_iff_reflects_isomorphisms_yoneda_obj CategoryTheory.isCodetector_iff_reflectsIsomorphisms_yoneda_obj

theorem wellPowered_of_isDetector [HasPullbacks C] (G : C) (hG : IsDetector G) : WellPowered C :=
  wellPowered_of_isDetecting hG
#align category_theory.well_powered_of_is_detector CategoryTheory.wellPowered_of_isDetector

end CategoryTheory