-- Mathlib/CategoryTheory/Sites/LocallyBijective.lean
/-- A morphism of sheaves of types is locally bijective iff it is an isomorphism.
(This is generalized below as `isLocallyBijective_iff_isIso`.) -/
private lemma isLocallyBijective_iff_isIso' :
    IsLocallyInjective f ∧ IsLocallySurjective f ↔ IsIso f

Proof state:
case a
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
H : Presieve.IsSheafFor F.val (Presheaf.imageSieve f.val s).arrows
t : Presieve.FamilyOfElements F.val (Presheaf.imageSieve f.val s).arrows :=
  fun Y g hg => Presheaf.localPreimage f.val s g hg
Y₁ Y₂ W : C
g₁ : W ⟶ Y₁
g₂ : W ⟶ Y₂
f₁ : Y₁ ⟶ Opposite.unop X
f₂ : Y₂ ⟶ Opposite.unop X
hf₁ : (Presheaf.imageSieve f.val s).arrows f₁
hf₂ : (Presheaf.imageSieve f.val s).arrows f₂
w : g₁ ≫ f₁ = g₂ ≫ f₂
eq₁ : f.val.app (Opposite.op W) (F.val.map g₁.op (t f₁ hf₁)) = G.val.map g₁.op (f.val.app (Opposite.op Y₁) (t f₁ hf₁))
eq₂ : f.val.app (Opposite.op W) (F.val.map g₂.op (t f₂ hf₂)) = G.val.map g₂.op (f.val.app (Opposite.op Y₂) (t f₂ hf₂))
eq₃ :
  G.val.map g₁.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₁))) (Presheaf.localPreimage f.val s f₁ hf₁)) =
    G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s)
eq₄ :
  G.val.map g₂.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₂))) (Presheaf.localPreimage f.val s f₂ hf₂)) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)
⊢ G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)

Tactic:
erw [← FunctorToTypes.map_comp_apply, ← FunctorToTypes.map_comp_apply]

Result:
case a
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
H : Presieve.IsSheafFor F.val (Presheaf.imageSieve f.val s).arrows
t : Presieve.FamilyOfElements F.val (Presheaf.imageSieve f.val s).arrows :=
  fun Y g hg => Presheaf.localPreimage f.val s g hg
Y₁ Y₂ W : C
g₁ : W ⟶ Y₁
g₂ : W ⟶ Y₂
f₁ : Y₁ ⟶ Opposite.unop X
f₂ : Y₂ ⟶ Opposite.unop X
hf₁ : (Presheaf.imageSieve f.val s).arrows f₁
hf₂ : (Presheaf.imageSieve f.val s).arrows f₂
w : g₁ ≫ f₁ = g₂ ≫ f₂
eq₁ : f.val.app (Opposite.op W) (F.val.map g₁.op (t f₁ hf₁)) = G.val.map g₁.op (f.val.app (Opposite.op Y₁) (t f₁ hf₁))
eq₂ : f.val.app (Opposite.op W) (F.val.map g₂.op (t f₂ hf₂)) = G.val.map g₂.op (f.val.app (Opposite.op Y₂) (t f₂ hf₂))
eq₃ :
  G.val.map g₁.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₁))) (Presheaf.localPreimage f.val s f₁ hf₁)) =
    G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s)
eq₄ :
  G.val.map g₂.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₂))) (Presheaf.localPreimage f.val s f₂ hf₂)) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)
⊢ G.val.map (f₁.op ≫ g₁.op) s = G.val.map (f₂.op ≫ g₂.op) s

Proof state:
case mp.intro
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
⊢ ∃ a, f.val.app X a = s

Tactic:
have H := (isSheaf_iff_isSheaf_of_type J F.val).1 F.cond _ (Presheaf.imageSieve_mem J f.val s)

Result:
case mp.intro
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
H : Presieve.IsSheafFor F.val (Presheaf.imageSieve f.val s).arrows
⊢ ∃ a, f.val.app X a = s

Proof state:
case a
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
H : Presieve.IsSheafFor F.val (Presheaf.imageSieve f.val s).arrows
t : Presieve.FamilyOfElements F.val (Presheaf.imageSieve f.val s).arrows :=
  fun Y g hg => Presheaf.localPreimage f.val s g hg
Y₁ Y₂ W : C
g₁ : W ⟶ Y₁
g₂ : W ⟶ Y₂
f₁ : Y₁ ⟶ Opposite.unop X
f₂ : Y₂ ⟶ Opposite.unop X
hf₁ : (Presheaf.imageSieve f.val s).arrows f₁
hf₂ : (Presheaf.imageSieve f.val s).arrows f₂
w : g₁ ≫ f₁ = g₂ ≫ f₂
eq₁ : f.val.app (Opposite.op W) (F.val.map g₁.op (t f₁ hf₁)) = G.val.map g₁.op (f.val.app (Opposite.op Y₁) (t f₁ hf₁))
eq₂ : f.val.app (Opposite.op W) (F.val.map g₂.op (t f₂ hf₂)) = G.val.map g₂.op (f.val.app (Opposite.op Y₂) (t f₂ hf₂))
eq₃ :
  G.val.map g₁.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₁))) (Presheaf.localPreimage f.val s f₁ hf₁)) =
    G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s)
eq₄ :
  G.val.map g₂.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₂))) (Presheaf.localPreimage f.val s f₂ hf₂)) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)
⊢ (ConcreteCategory.hom (f.val.app (Opposite.op W))) (F.val.map g₁.op (t f₁ hf₁)) =
    (ConcreteCategory.hom (f.val.app (Opposite.op W))) (F.val.map g₂.op (t f₂ hf₂))

Tactic:
refine eq₁.trans (eq₃.trans (Eq.trans ?_ (eq₄.symm.trans eq₂.symm)))

Result:
case a
C : Type u
inst✝ : Category.{v, u} C
J : GrothendieckTopology C
F G : Sheaf J (Type w)
f : F ⟶ G
h₁ : ∀ (X : Cᵒᵖ), Function.Injective ⇑(ConcreteCategory.hom (f.val.app X))
right✝ : IsLocallySurjective f
X : Cᵒᵖ
s : G.val.obj X
H : Presieve.IsSheafFor F.val (Presheaf.imageSieve f.val s).arrows
t : Presieve.FamilyOfElements F.val (Presheaf.imageSieve f.val s).arrows :=
  fun Y g hg => Presheaf.localPreimage f.val s g hg
Y₁ Y₂ W : C
g₁ : W ⟶ Y₁
g₂ : W ⟶ Y₂
f₁ : Y₁ ⟶ Opposite.unop X
f₂ : Y₂ ⟶ Opposite.unop X
hf₁ : (Presheaf.imageSieve f.val s).arrows f₁
hf₂ : (Presheaf.imageSieve f.val s).arrows f₂
w : g₁ ≫ f₁ = g₂ ≫ f₂
eq₁ : f.val.app (Opposite.op W) (F.val.map g₁.op (t f₁ hf₁)) = G.val.map g₁.op (f.val.app (Opposite.op Y₁) (t f₁ hf₁))
eq₂ : f.val.app (Opposite.op W) (F.val.map g₂.op (t f₂ hf₂)) = G.val.map g₂.op (f.val.app (Opposite.op Y₂) (t f₂ hf₂))
eq₃ :
  G.val.map g₁.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₁))) (Presheaf.localPreimage f.val s f₁ hf₁)) =
    G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s)
eq₄ :
  G.val.map g₂.op ((ConcreteCategory.hom (f.val.app (Opposite.op Y₂))) (Presheaf.localPreimage f.val s f₂ hf₂)) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)
⊢ G.val.map g₁.op ((ConcreteCategory.hom (G.val.map f₁.op)) s) =
    G.val.map g₂.op ((ConcreteCategory.hom (G.val.map f₂.op)) s)