{-# OPTIONS --safe #-}
module Cubical.Homotopy.Connected where

open import Cubical.Foundations.Prelude
open import Cubical.Foundations.Function
open import Cubical.Foundations.Isomorphism
open import Cubical.Foundations.Equiv
open import Cubical.Foundations.Equiv.Properties
open import Cubical.Foundations.Pointed
open import Cubical.Foundations.Transport
open import Cubical.Foundations.GroupoidLaws
open import Cubical.Foundations.HLevels
open import Cubical.Foundations.Path
open import Cubical.Foundations.Univalence

open import Cubical.Functions.Fibration
open import Cubical.Functions.FunExtEquiv
open import Cubical.Functions.Surjection

open import Cubical.Data.Unit
open import Cubical.Data.Bool hiding (elim; _≤_)
open import Cubical.Data.Nat hiding (elim)
open import Cubical.Data.Nat.Order
open import Cubical.Data.NatMinusOne
open import Cubical.Data.Sigma
open import Cubical.Data.Prod.Properties

open import Cubical.HITs.Nullification hiding (elim)
open import Cubical.HITs.Susp
open import Cubical.HITs.SmashProduct
open import Cubical.HITs.Pushout
open import Cubical.HITs.Join
open import Cubical.HITs.Sn.Base
open import Cubical.HITs.S1 hiding (elim)
open import Cubical.HITs.Truncation as Trunc
  renaming (rec to trRec) hiding (elim)

open import Cubical.Homotopy.Loopspace

private
  variable
     : Level
    X₀ X₁ X₂ Y₀ Y₁ Y₂ : Type 

-- Note that relative to most sources, this notation is off by +2
isConnected :  {} (n : HLevel) (A : Type )  Type 
isConnected n A = isContr (hLevelTrunc n A)

isConnectedFun :  { ℓ'} (n : HLevel) {A : Type } {B : Type ℓ'} (f : A  B)  Type (ℓ-max  ℓ')
isConnectedFun n f =  b  isConnected n (fiber f b)

isConnectedZero :  {} (A : Type )  isConnected 0 A
isConnectedZero A = isContrUnit*

isConnectedSubtr :  {} {A : Type } (n m : HLevel)
                 isConnected (m + n) A
                 isConnected n A
isConnectedSubtr {A = A} n m iscon =
  isOfHLevelRetractFromIso 0 (truncOfTruncIso n m) (helper n iscon)
  where
  helper : (n : )  isConnected (m + n) A  isContr (hLevelTrunc n (hLevelTrunc (m + n) A))
  helper zero iscon = isContrUnit*
  helper (suc n) iscon =  iscon .fst  , (Trunc.elim  _  isOfHLevelPath (suc n) (isOfHLevelTrunc (suc n)) _ _) λ a  cong ∣_∣ (iscon .snd a))

isConnectedFunSubtr :  { ℓ'} {A : Type } {B : Type ℓ'} (n m : HLevel) (f : A  B)
                 isConnectedFun (m + n) f
                 isConnectedFun n f
isConnectedFunSubtr n m f iscon b = isConnectedSubtr n m (iscon b)

isConnectedFun≤ :  { ℓ'} {A : Type } {B : Type ℓ'} (n m : HLevel) (f : A  B)
   n  m  isConnectedFun m f  isConnectedFun n f
isConnectedFun≤ n m f hnm hf =
  isConnectedFunSubtr n (hnm .fst) f
    (subst  d  isConnectedFun d f) (sym (hnm .snd)) hf)

private
  typeToFiberIso :  {} (A : Type )  Iso A (fiber  (x : A)  tt) tt)
  Iso.fun (typeToFiberIso A) x = x , refl
  Iso.inv (typeToFiberIso A) = fst
  Iso.rightInv (typeToFiberIso A) b i = fst b , (isOfHLevelSuc 1 (isPropUnit) tt tt (snd b) refl) i
  Iso.leftInv (typeToFiberIso A) a = refl

  typeToFiber :  {} (A : Type )  A  fiber  (x : A)  tt) tt
  typeToFiber A = isoToPath (typeToFiberIso A)

isConnectedFun→isConnected : {X : Type } (n : HLevel)
   isConnectedFun n  (_ : X)  tt)  isConnected n X
isConnectedFun→isConnected n h =
  subst (isConnected n) (sym (typeToFiber _)) (h tt)

isConnected→isConnectedFun : {X : Type } (n : HLevel)
   isConnected n X  isConnectedFun n  (_ : X)  tt)
isConnected→isConnectedFun n h = λ { tt  subst (isConnected n) (typeToFiber _) h }

isOfHLevelIsConnectedStable :  {} {A : Type } (n : )
   isOfHLevel n (isConnected n A)
isOfHLevelIsConnectedStable {A = A} zero =
  (tt* ,  _  refl)) , λ _  refl
isOfHLevelIsConnectedStable {A = A} (suc n) =
  isProp→isOfHLevelSuc n isPropIsContr

module elim { ℓ' : Level} {A : Type } {B : Type ℓ'} (f : A  B) where
  private
    inv :  {ℓ'''} (n : HLevel) (P : B  TypeOfHLevel ℓ''' (suc n))
         ((a : A)  P (f a) .fst)
         (b : B)
         hLevelTrunc (suc n) (fiber f b)  P b .fst
    inv n P t b =
      Trunc.rec
        (P b .snd)
         {(a , p)  subst (fst  P) p (t a)})

  isIsoPrecompose :  {ℓ'''} (n : ) (P : B  TypeOfHLevel ℓ''' n)
                    isConnectedFun n f
                    Iso ((b : B)  P b .fst) ((a : A)  P (f a) .fst)
  Iso.fun (isIsoPrecompose _ P fConn) = _∘ f
  Iso.inv (isIsoPrecompose zero P fConn) =
    Iso.inv (isContr→Iso' (isOfHLevelΠ _  b  P b .snd)) (isOfHLevelΠ _ λ a  P (f a) .snd) (_∘ f))
  Iso.rightInv (isIsoPrecompose zero P fConn) =
    Iso.rightInv (isContr→Iso' (isOfHLevelΠ _  b  P b .snd)) (isOfHLevelΠ _ λ a  P (f a) .snd) (_∘ f))
  Iso.leftInv (isIsoPrecompose zero P fConn) =
    Iso.leftInv (isContr→Iso' (isOfHLevelΠ _  b  P b .snd)) (isOfHLevelΠ _ λ a  P (f a) .snd) (_∘ f))
  Iso.inv (isIsoPrecompose (suc n) P fConn) t b = inv n P t b (fConn b .fst)
  Iso.rightInv (isIsoPrecompose (suc n) P fConn) t =
    funExt λ a  cong (inv n P t (f a)) (fConn (f a) .snd  a , refl )
                substRefl {B = fst  P} (t a)
  Iso.leftInv (isIsoPrecompose (suc n) P fConn) s =
    funExt λ b 
          Trunc.elim
            {B = λ d  inv n P (s  f) b d  s b}
             _  isOfHLevelPath (suc n) (P b .snd) _ _)
             {(a , p) i  transp  j  P (p (j  i)) .fst) i (s (p i))})
            (fConn b .fst)

  isIsoPrecomposeβ :  {ℓ'''} (n : ) (P : B  TypeOfHLevel ℓ''' n)
                    (e : isConnectedFun n f)
                    (g : ((a : A)  P (f a) .fst))
                    (a : A)
                    Iso.inv (isIsoPrecompose n P e) g (f a)  g a
  isIsoPrecomposeβ zero P e g a = P (f a) .snd .snd (g a)
  isIsoPrecomposeβ (suc n) P e g a =
      cong (inv n P g (f a)) (e (f a) .snd  a , refl )
     transportRefl _

  isEquivPrecompose :  {ℓ'''} (n : ) (P : B  TypeOfHLevel ℓ''' n)
                    isConnectedFun n f
                    isEquiv (λ(s : (b : B)  P b .fst)  s  f)
  isEquivPrecompose zero P fConn = isoToIsEquiv theIso
    where
    theIso : Iso ((b : B)  P b .fst) ((a : A)  P (f a) .fst)
    Iso.fun theIso = λ(s : (b : B)  P b .fst)  s  f
    Iso.inv theIso = λ _ b  P b .snd .fst
    Iso.rightInv theIso g = funExt λ x  P (f x) .snd .snd (g x)
    Iso.leftInv theIso g = funExt λ x  P x .snd .snd (g x)
  isEquivPrecompose (suc n) P fConn = isoToIsEquiv (isIsoPrecompose (suc n) P fConn)

  isConnectedPrecompose : (n : )  ((P : B  TypeOfHLevel (ℓ-max  ℓ') n)
                                     hasSection (λ(s : (b : B)  P b .fst)  s  f))
                        isConnectedFun n f
  isConnectedPrecompose zero P→sect b = isContrUnit*
  isConnectedPrecompose (suc n) P→sect b = c n P→sect b , λ y   sym (fun n P→sect b y)
    where
    P : (n : HLevel)  ((P : B  TypeOfHLevel  (suc n))
      hasSection (λ(s : (b : B)  P b .fst)  s  f))
      B  Type _
    P n s b = hLevelTrunc (suc n) (fiber f b)

    c : (n : HLevel)  ((P : B  TypeOfHLevel (ℓ-max  ℓ') (suc n))
      hasSection (λ(s : (b : B)  P b .fst)  s  f))  (b : B)
      hLevelTrunc (suc n) (fiber f b)
    c n s = (s λ b  (hLevelTrunc (suc n) (fiber f b) , isOfHLevelTrunc _)) .fst
              λ a   a , refl 

    fun : (n : HLevel) (P→sect : ((P : B  TypeOfHLevel (ℓ-max  ℓ') (suc n))
                      hasSection λ(s : (b : B)  P b .fst)  s  f))
       (b : B) (w : (hLevelTrunc (suc n) (fiber f b)))
       w  c n P→sect b
    fun n P→sect b = Trunc.elim  x  isOfHLevelPath (suc n) (isOfHLevelTrunc _) _ _)
                                       λ a  J  b p   (fst a) , p   c n P→sect b)
                                               (c* (fst a))
                                               (snd a)
        where
        c* : ((a : A)   (a , refl {x = f a})   c n P→sect (f a))
        c* a = sym (cong  x  x a) (P→sect  b  hLevelTrunc (suc n) (fiber f b) , isOfHLevelTrunc _) .snd λ a   a , refl ))

isOfHLevelPrecomposeConnected :  { ℓ' ℓ''} (k : HLevel) (n : HLevel)
  {A : Type } {B : Type ℓ'} (P : B  TypeOfHLevel ℓ'' (k + n)) (f : A  B)
   isConnectedFun n f
   isOfHLevelFun k (λ(s : (b : B)  P b .fst)  s  f)
isOfHLevelPrecomposeConnected zero n P f fConn =
  elim.isEquivPrecompose f n P fConn .equiv-proof
isOfHLevelPrecomposeConnected (suc k) n P f fConn t =
  isOfHLevelPath'⁻ k
    λ {(s₀ , p₀) (s₁ , p₁) 
      isOfHLevelRetractFromIso k (invIso ΣPathIsoPathΣ)
         (subst (isOfHLevel k)
           (sym (fiberPath (s₀ , p₀) (s₁ , p₁)))
           (isOfHLevelRetract k
             {(q , α)  (funExt⁻ q) , (cong funExt⁻ α)})
             {(h , β)  (funExt h) , (cong funExt β)})
             _  refl)
            (isOfHLevelPrecomposeConnected k n
               b  (s₀ b  s₁ b) , isOfHLevelPath' (k + n) (P b .snd) _ _)
              f fConn
              (funExt⁻ (p₀ ∙∙ refl ∙∙ sym p₁)))))}

indMapEquiv→conType :  {} {A : Type } (n : HLevel)
                    ((B : TypeOfHLevel  n)
                       isEquiv  (b : (fst B))  λ (a : A)  b))
                    isConnected n A
indMapEquiv→conType {A = A} zero BEq = isContrUnit*
indMapEquiv→conType {A = A} (suc n) BEq =
  isOfHLevelRetractFromIso 0 (mapCompIso {n = (suc n)} (typeToFiberIso A))
    (elim.isConnectedPrecompose  _  tt) (suc n)
                                 P  ((λ a _  a)  invIsEq (BEq (P tt)))
                               , λ a  equiv-proof (BEq (P tt)) a .fst .snd)
                                tt)

isConnectedComp :  { ℓ' ℓ''} {A : Type } {B : Type ℓ'} {C : Type ℓ''}
     (f : B  C) (g : A  B) (n : )
    isConnectedFun n f
    isConnectedFun n g
    isConnectedFun n (f  g)
isConnectedComp  {C = C} f g n con-f con-g =
  elim.isConnectedPrecompose (f  g) n
    λ P 
      isEquiv→hasSection
        (compEquiv
         (_ , elim.isEquivPrecompose f n P con-f)
         (_ , elim.isEquivPrecompose g n  b  P (f b)) con-g) .snd)

isConnectedFunCancel :  {} {X Y Z : Type } (f : X  Y) (g : Y  Z) (n : HLevel)
   isConnectedFun n f  isConnectedFun (1 + n) (g  f)  isConnectedFun (1 + n) g
isConnectedFunCancel { = } {X = X} {Y = Y} {Z = Z} f g n nconf con∘ =
  elim.isConnectedPrecompose g (suc n)
    λ P  (d P) , d-sec P
  where
    module _ (P : Z  TypeOfHLevel  (suc n)) where
      d : ((a : Y)  P (g a) .fst)  (b : Z)  P b .fst
      d F z =
        equiv-proof (elim.isEquivPrecompose (g  f) (suc n) P con∘)
           x  F (f x))
          .fst .fst z

      d-sec : section  s  s  g) d
      d-sec F =
        funExt
          (equiv-proof
            (elim.isEquivPrecompose f n
              x  ((d F  g) x  F x) , isOfHLevelPath' n (P (g x) .snd) _ _) nconf)
              a   i  rec₊ (P (g (f a)) .snd)
                                  { (a , p)  subst  x  fst (P x)) p (F (f a)) })
                                 (con∘ (g (f a)) .snd  a , refl  i))
                    transportRefl (F (f a)))
          .fst .fst)

isConnectedFunCancel' : (f : X₀  X₁) (g : X₁  X₂) (n : HLevel)
   isConnectedFun (1 + n) g  isConnectedFun n (g  f)  isConnectedFun n f
isConnectedFunCancel' f g zero con-g con-f b =
  tt* ,  {tt*  refl})
isConnectedFunCancel' {X₀ = X} {X₁ = Y} {X₂ = Z} f g (suc n) con-g con-f =
  elim.isConnectedPrecompose f (suc n)
    λ P  d P , d-sec P
  where
  ℓ' : Level
  ℓ' = _
  module _ (P : Y  TypeOfHLevel ℓ' (suc n)) where
    pre-d : (y : Y) (a : X) (p : _)   Path (fiber g (g y)) (y , refl) (f a , p)  (suc n)
    pre-d y a p =
      Iso.fun (PathIdTruncIso (suc n))
        (isContr→isProp (con-g (g y))  y , refl   (f a , p) )

    pre-d-refl : (x : X)  pre-d (f x) x refl    b  f x , refl) 
    pre-d-refl x =
         i  Iso.fun (PathIdTruncIso (suc n))
                 (isProp→isSet (isContr→isProp (con-g (g (f x)))) _ _
                  (isContr→isProp (con-g (g (f x))) _ _) refl i))
       cong ∣_∣ₕ (transportRefl  b  f x , refl))

    d : ((a : X)  P (f a) .fst)  (b : Y)  P b .fst
    d F y = Trunc.rec (snd (P y))
             {(a , p) 
              Trunc.rec (P y .snd)
                 p  subst (fst  P) (cong fst (sym p)) (F a))
                (pre-d y a p)})
            (con-f (g y) .fst)

    d-sec : section  s  s  f) d
    d-sec F =
      funExt λ x
          i  Trunc.rec (snd (P (f x)))
             {(a , p) 
              Trunc.rec (P (f x) .snd)
                 p  subst (fst  P) (cong fst (sym p)) (F a))
                (pre-d (f x) a p)})
            (con-f (g (f x)) .snd  x , refl  i))
            i  Trunc.rec (P (f x) .snd)
                p  subst (fst  P) (cong fst (sym p)) (F x))
               (pre-d-refl x i))
           transportRefl (F x)

isEquiv→isConnected :  { ℓ'} {A : Type } {B : Type ℓ'}
    (f : A  B)
   isEquiv f
   (n : )  isConnectedFun n f
isEquiv→isConnected f iseq n b =
  isContr→isContr∥ n (equiv-proof iseq b)

isConnectedId :  {} {A : Type } {n : HLevel}  isConnectedFun n (idfun A)
isConnectedId = isEquiv→isConnected _ (idIsEquiv _) _


isConnectedPath :  {} (n : HLevel) {A : Type }
   isConnected (suc n) A
   (a₀ a₁ : A)  isConnected n (a₀  a₁)
isConnectedPath zero connA a₀ a₁ = isContrUnit*
isConnectedPath (suc n) {A = A} connA a₀ a₁ =
  isOfHLevelRetractFromIso 0 (invIso (PathIdTruncIso (suc n))) (isContr→isContrPath connA _ _)

isConnectedPathP :  {} (n : HLevel) {A : I  Type }
   isConnected (suc n) (A i1)
   (a₀ : A i0) (a₁ : A i1)  isConnected n (PathP A a₀ a₁)
isConnectedPathP n con a₀ a₁ =
  subst (isConnected n) (sym (PathP≡Path _ _ _))
        (isConnectedPath n con _ _)

isConnectedCong :  { ℓ'} (n : HLevel) {A : Type } {B : Type ℓ'} (f : A  B)
   isConnectedFun (suc n) f
    {a₀ a₁}  isConnectedFun n {A = a₀  a₁} (cong f)
isConnectedCong n f cf {a₀} {a₁} q =
  subst (isConnected n)
    (sym (fiberCong f q))
    (isConnectedPath n (cf (f a₁)) (a₀ , q) (a₁ , refl))

isConnectedCong' :  { ℓ'} {A : Type } {B : Type ℓ'} {x : A} {y : B}
     (n : ) (f : A  B)
   isConnectedFun (suc n) f
   (p : f x  y)
   isConnectedFun n
      λ (q : x  x)  sym p ∙∙ cong f q ∙∙ p
isConnectedCong' {x = x} n f conf p =
  transport  i  (isConnectedFun n
                    λ (q : x  x)
                     doubleCompPath-filler (sym p) (cong f q) p i))
    (isConnectedCong n f conf)

isConnectedΩ→ :  { ℓ'} {A : Pointed } {B : Pointed ℓ'} (n : )
   (f : A →∙ B)
   isConnectedFun (suc n) (fst f)
   isConnectedFun n (fst (Ω→ f))
isConnectedΩ→ n f g =
  transport  i  isConnectedFun n λ b
                  doubleCompPath-filler (sym (snd f)) (cong (fst f) b) (snd f) i)
            (isConnectedCong n _ g)

isConnectedΩ^→ :  { ℓ'} {A : Pointed } {B : Pointed ℓ'} (n m : )
   (f : A →∙ B)
   isConnectedFun (suc n) (fst f)
   isConnectedFun ((suc n)  m) (fst (Ω^→ m f))
isConnectedΩ^→ n zero f conf = conf
isConnectedΩ^→ n (suc zero) f conf = isConnectedΩ→ n f conf
isConnectedΩ^→ {A = A} {B = B} n (suc (suc m)) f conf =
  transport  i  isConnectedFun (suc n  suc (suc m))
    λ q  doubleCompPath-filler
            (sym (snd (Ω^→ (suc m) f)))
            (cong (fst (Ω^→ (suc m) f)) q)
            (snd (Ω^→ (suc m) f)) i)
    (main n m (isConnectedΩ^→ n (suc m) f conf))
  where
  open import Cubical.Data.Sum
  lem : (n m : )  ((suc n  m  suc (n  m)))  (suc n  suc m  0)
  lem n zero = inl refl
  lem zero (suc m) = inr refl
  lem (suc n) (suc m) = lem n m

  main : (n m : )
       isConnectedFun (suc n  suc m) (fst (Ω^→ (suc m) f))
       isConnectedFun (suc n  suc (suc m))
          {A = Path ((Ω^ (suc m)) (_ , pt A) .fst)
          refl refl}
          (cong (fst (Ω^→ (suc m) f)))
  main n m conf with (lem n (suc m))
  ... | (inl x) =
    isConnectedCong (n  suc m) (fst (Ω^→ (suc m) f))
      (subst  x  isConnectedFun x (fst (Ω^→ (suc m) f))) x conf)
  ... | (inr x) =
    subst  x  isConnectedFun x (cong {x = refl} {y = refl}
                   (fst (Ω^→ (suc m) f))))
      (sym x)
      λ b  tt* , λ _  refl

isConnectedRetract :  { ℓ'} (n : HLevel)
  {A : Type } {B : Type ℓ'}
  (f : A  B) (g : B  A)
  (h : (x : A)  g (f x)  x)
   isConnected n B  isConnected n A
isConnectedRetract zero _ _ _ _ = isContrUnit*
isConnectedRetract (suc n) f g h =
  isContrRetract
    (Trunc.map f)
    (Trunc.map g)
    (Trunc.elim
       _  isOfHLevelPath (suc n) (isOfHLevelTrunc (suc n)) _ _)
       a  cong ∣_∣ (h a)))

isConnectedRetractFromIso :   { ℓ'} (n : HLevel)
    {A : Type } {B : Type ℓ'}
   Iso A B
   isConnected n B  isConnected n A
isConnectedRetractFromIso n e =
  isConnectedRetract n
    (Iso.fun e)
    (Iso.inv e)
    (Iso.leftInv e)

isConnectedPoint :  {} (n : HLevel) {A : Type }
   isConnected (suc n) A
   (a : A)  isConnectedFun n (λ(_ : Unit)  a)
isConnectedPoint n connA a₀ a =
  isConnectedRetract n
    snd (_ ,_)  _  refl)
    (isConnectedPath n connA a₀ a)

isConnectedPoint2 :  {} (n : HLevel) {A : Type } (a : A)
    isConnectedFun n (λ(_ : Unit)  a)
    isConnected (suc n) A
isConnectedPoint2 n {A = A} a connMap = indMapEquiv→conType _ λ B  isoToIsEquiv (theIso B)
  where
  module _  {ℓ' : Level} (B : TypeOfHLevel ℓ' (suc n))
    where
    helper : (f : A  fst B)  (a2 : A)  f a2  f a
    helper f = equiv-proof (elim.isEquivPrecompose  _  a) n  a2  (f a2  f a) ,
                             isOfHLevelPath' n (snd B) (f a2) (f a)) connMap)  _  refl) .fst .fst

    theIso : Iso (fst B) (A  fst B)
    Iso.fun theIso b a = b
    Iso.inv theIso f = f a
    Iso.rightInv theIso f = funExt λ y  sym (helper f y)
    Iso.leftInv theIso _ = refl

module isConnectedPoint { ℓ'} (n : HLevel) {A : Type }
     {B : A  Type ℓ'}
     (conA : isConnected (suc n) A)
     (hlevB : (a : A)  isOfHLevel n (B a))
     (p : Σ[ a  A ] (B a)) where
  private
    module m = elim  (tt : Unit)  fst p)
    P : A  TypeOfHLevel ℓ' n
    P a = B a , hlevB a
    con* = isConnectedPoint n conA (fst p)

  elim : (a : A)  B a
  elim = Iso.inv (m.isIsoPrecompose n P con*) λ _  snd p

  β : elim (fst p)  snd p
  β = m.isIsoPrecomposeβ n P con*  _  snd p) tt

connectedTruncIso :  {} {A B : Type } (n : HLevel) (f : A  B)
                    isConnectedFun n f
                    Iso (hLevelTrunc n A) (hLevelTrunc n B)
connectedTruncIso {A = A} {B = B} zero f con = isContr→Iso isContrUnit* isContrUnit*
connectedTruncIso {A = A} {B = B} (suc n) f con = g
  where
  back : B  hLevelTrunc (suc n) A
  back y = map fst ((con y) .fst)

  backSection :  (b : B)  Path (hLevelTrunc (suc n) B)
                                (Trunc.rec (isOfHLevelTrunc (suc n))
                                            a   f a )
                                           (Trunc.rec (isOfHLevelTrunc (suc n))
                                                      back  b ))
                                b 
  backSection b = helper  p  map f p   b )
                          x  (isOfHLevelSuc (suc n) (isOfHLevelTrunc (suc n)))
                                   (map f (map fst x))  b )
                          a p  cong ∣_∣ p)
                         (fst (con b))
    where
    helper :  { ℓ' ℓ''} {A : Type } {B : A  Type ℓ'} (P : hLevelTrunc (suc n) A  Type ℓ'')
            ((x : hLevelTrunc (suc n) (Σ A B))  isOfHLevel (suc n) (P (map fst x)))
            ((a : A) (b : B a)  P  a )
            (p : hLevelTrunc (suc n) (Σ A B))
             P (map fst p)
    helper P hlev pf = Trunc.elim hlev λ pair  pf (fst pair) (snd pair)

  g : Iso (hLevelTrunc (suc n) A) (hLevelTrunc (suc n) B)
  Iso.fun g = map f
  Iso.inv g = Trunc.rec (isOfHLevelTrunc _) back
  Iso.leftInv g = Trunc.elim  x  isOfHLevelPath (suc n) (isOfHLevelTrunc _) _ _)
                               λ a  cong (map fst) (con (f a) .snd   a , refl )
  Iso.rightInv g = Trunc.elim  x  isOfHLevelPath (suc n) (isOfHLevelTrunc _) _ _)
                              backSection

connectedTruncIso2 :  {} {A B : Type } (n m : HLevel) (f : A  B)
                    Σ[ x   ] x + n  m
                    isConnectedFun m f
                    Iso (hLevelTrunc n A) (hLevelTrunc n B)
connectedTruncIso2 {A = A} {B = B} n m f (x , pf) con =
  connectedTruncIso n f (isConnectedFunSubtr n x f (transport  i  isConnectedFun (pf (~ i)) f) con))

connectedTruncEquiv :  {} {A B : Type } (n : HLevel) (f : A  B)
                    isConnectedFun n f
                    hLevelTrunc n A  hLevelTrunc n B
connectedTruncEquiv {A = A} {B = B} n f con = isoToEquiv (connectedTruncIso n f con)

isConnectedSuc→isSurjection :  { ℓ'} {A : Type } {B : Type ℓ'} {f : A  B} {n : HLevel}
   isConnectedFun (suc n) f  isSurjection f
isConnectedSuc→isSurjection hf b =
  Iso.inv propTruncTrunc1Iso (isConnectedFun≤ 1 _ _ (suc-≤-suc zero-≤) hf b .fst)

isConnectedSuspFun :  { ℓ'} {X : Type } {Y : Type ℓ'}
       (f : X  Y) (n : HLevel)
     isConnectedFun n f
     isConnectedFun (suc n) (suspFun f)
isConnectedSuspFun {X = X} {Y = Y} f n con-f =
  elim.isConnectedPrecompose _ (suc n)
    λ P  d P , d-sec P
  where
  ℓ'' : Level
  ℓ'' = _
  module _ (P : Susp Y  TypeOfHLevel ℓ'' (suc n)) where
    module _ (a : _) (F : ((a : Susp X)  P (suspFun f a) .fst)) where
      d-pre₁ : fiber f a  PathP  i  P (merid a i) .fst) (F north) (F south)
      d-pre₁ (x , p) =
        subst  a  PathP  i₁  P (merid a i₁) .fst) (F north) (F south)) p
         (cong F (merid x))

      d-pre₂ :  hLevelTrunc n (fiber f a)
              PathP  i  P (merid a i) .fst) (F north) (F south)
      d-pre₂ s =
        trRec (isOfHLevelPathP' n (snd (P south)) _ _)
              (d-pre₁)
              s

    d : ((a : Susp X)  P (suspFun f a) .fst)  (b : Susp Y)  P b .fst
    d F north = F north
    d F south = F south
    d F (merid a i) = d-pre₂ a F (con-f a .fst) i

    d-sec : section  s  s   z  suspFun f z)) d
    d-sec F =
      funExt λ { north  refl
               ; south  refl
               ; (merid a i) j  help a j i}
      where
      help : (a : _)  cong (d F  suspFun f) (merid a)  cong F (merid a)
      help a =
         i  d-pre₂ (f a) F (con-f (f a) .snd  a , refl ∣ₕ i))
         recₕ n (a , refl)
         transportRefl (cong F (merid a))

isConnectedSusp :  {} {X : Type } (n : HLevel)
   isConnected n X
   isConnected (suc n) (Susp X)
isConnectedSusp {X = X} n h = isConnectedFun→isConnected (suc n) $
  isConnectedComp _ (suspFun  (x : X)  tt)) (suc n)
    (isEquiv→isConnected _ (equivIsEquiv (invEquiv Unit≃SuspUnit)) (suc n))
    (isConnectedSuspFun _ n (isConnected→isConnectedFun n h))

-- See also `sphereConnected` for S₊
isConnectedSphere :  (n : )  isConnected n (S (-1+ n))
isConnectedSphere zero = isConnectedZero (S (-1+ 0))
isConnectedSphere (suc n) = isConnectedSusp n (isConnectedSphere n)

isConnected-map-× :  { ℓ' ℓ''} {A : Type } {B : Type ℓ'} {C : Type ℓ''} (n : HLevel)
  (f : B  C)  isConnectedFun n f  isConnectedFun n (map-× (idfun A) f)
isConnected-map-× n f hf z =
  isConnectedRetractFromIso _ (invIso $ fiber-map-× f (fst z) (snd z)) (hf (snd z))

-- TODO : Reorganise the following proofs.

inrConnected :  { ℓ' ℓ''} {A : Type } {B : Type ℓ'} {C : Type ℓ''} (n : HLevel)
             (f : C  A) (g : C  B)
             isConnectedFun n f
             isConnectedFun n {A = B} {B = Pushout f g} inr
inrConnected {A = A} {B = B} {C = C} n f g iscon =
  elim.isConnectedPrecompose inr n λ P  (k P) , λ b  refl
  where
  module _ { : Level} (P : (Pushout f g)  TypeOfHLevel  n)
                   (h : (b : B)  typ (P (inr b)))
    where
    Q : A  TypeOfHLevel _ n
    Q a = (P (inl a))

    fun : (c : C)  typ (Q (f c))
    fun c = transport  i  typ (P (push c (~ i)))) (h (g c))

    k : (d : Pushout f g)  typ (P d)
    k (inl x) = equiv-proof (elim.isEquivPrecompose f n Q iscon) fun .fst .fst x
    k (inr x) = h x
    k (push a i) =
      hcomp  k  λ { (i = i0)  equiv-proof (elim.isEquivPrecompose f n Q iscon) fun .fst .snd i0 a
                     ; (i = i1)  transportTransport⁻  j  typ (P (push a j))) (h (g a)) k })
            (transp  j  typ (P (push a (i  j))))
                    (~ i)
                    (equiv-proof (elim.isEquivPrecompose f n Q iscon)
                                 fun .fst .snd i a))

isConnectedPushout→ :
  (f₁ : X₀  X₁) (f₂ : X₀  X₂) (g₁ : Y₀  Y₁) (g₂ : Y₀  Y₂)
  (h₀ : X₀  Y₀) (h₁ : X₁  Y₁) (h₂ : X₂  Y₂)
  (e₁ : h₁  f₁  g₁  h₀) (e₂ : h₂  f₂  g₂  h₀)
  (n : HLevel)
   isConnectedFun n h₀  isConnectedFun (1 + n) h₁  isConnectedFun (1 + n) h₂
   isConnectedFun (1 + n) (Pushout→ f₁ f₂ g₁ g₂ h₀ h₁ h₂ e₁ e₂)
isConnectedPushout→ f₁ f₂ g₁ g₂ h₀ h₁ h₂ e₁ e₂ n con₀ con₁ con₂ =
  elim.isConnectedPrecompose _ (suc n)
    λ P  d P , d-sec P
  where
  ℓ' : Level
  ℓ' = _

  Push→ = Pushout→ f₁ f₂ g₁ g₂ h₀ h₁ h₂ e₁ e₂
  module _ (P : Pushout g₁ g₂  TypeOfHLevel ℓ' (suc n)) where
    module _ (F : ((a : Pushout f₁ f₂) 
       P (Pushout→ f₁ f₂ g₁ g₂ h₀ h₁ h₂ e₁ e₂ a) .fst) ) where
       incₗ : (x : _)  hLevelTrunc (suc n) (fiber h₁ x)  P (inl x) .fst
       incₗ x = Trunc.rec (snd (P _))
                  (uncurry  a  J  x y  fst (P (inl x))) (F (inl a))))

       incᵣ : (x : _)  hLevelTrunc (suc n) (fiber h₂ x)  P (inr x) .fst
       incᵣ x = Trunc.rec (snd (P _))
                  (uncurry  a  J  x y  fst (P (inr x))) (F (inr a))))

       push-lem : (x : _)
          PathP  i₁  P (push (h₀ x) i₁) .fst)
                  (incₗ (g₁ (h₀ x))  f₁ x , funExt⁻ e₁ x )
                  (incᵣ (g₂ (h₀ x))  f₂ x , funExt⁻ e₂ x )
       push-lem x i =
         comp  k  P (doubleCompPath-filler
                          j  inl (e₁ j x))
                         (push (h₀ x))
                          j  inr (e₂ (~ j) x)) (~ k) i) .fst)
               k  λ {(i = i0)  transp  i₁  fst (P (inl (e₁ (i₁  k) x))))
                                       (~ k) (F (inl (f₁ x)))
                       ; (i = i1)  transp  i₁  fst (P (inr (e₂ (i₁  k) x))))
                                       (~ k) (F (inr (f₂ x)))})
              (F (push x i))

       pre-pushFun : (x : _)  PathP  i₁  P (push (h₀ x) i₁) .fst)
                                 (incₗ (g₁ (h₀ x)) (con₁ (g₁ (h₀ x)) .fst))
                                 (incᵣ (g₂ (h₀ x)) (con₂ (g₂ (h₀ x)) .fst))
       pre-pushFun x = cong (incₗ (g₁ (h₀ x)))
                   (con₁ (g₁ (h₀ x)) .snd  (f₁ x) , (funExt⁻ e₁ x) )
                 (push-lem x)
                 (cong (incᵣ (g₂ (h₀ x)))
                   (sym (con₂ (g₂ (h₀ x)) .snd  (f₂ x) , (funExt⁻ e₂ x) )))

       pushFun : (a : _)  hLevelTrunc n (fiber h₀ a)
          PathP  i  P (push a i) .fst)
                  (incₗ (g₁ a) (con₁ (g₁ a) .fst))
                  (incᵣ (g₂ a) (con₂ (g₂ a) .fst))
       pushFun a =
         trRec (isOfHLevelPathP' n (snd (P _)) _ _)
               (uncurry λ x  J  a y 
                 PathP  i₁  P (push a i₁) .fst)
                  (incₗ (g₁ a) (con₁ (g₁ a) .fst))
                  (incᵣ (g₂ a) (con₂ (g₂ a) .fst)))
                (pre-pushFun x))

    d : ((a : Pushout f₁ f₂)  P (Push→ a) .fst) 
        (b : Pushout g₁ g₂)  P b .fst
    d F (inl x) = incₗ F x (con₁ x .fst)
    d F (inr x) = incᵣ F x (con₂ x .fst)
    d F (push a i) = pushFun F a (con₀ a .fst) i

    d-sec : section  s  s  Push→) d
    d-sec F =
      funExt λ { (inl x)  cong (incₗ F (h₁ x)) (con₁ (h₁ x) .snd  x , refl )
                            transportRefl (F (inl x))
               ; (inr x)  cong (incᵣ F (h₂ x)) (con₂ (h₂ x) .snd  x , refl )
                            transportRefl (F (inr x))
               ; (push a i)  push-case a i}
      where
      push-case : (a : _) 
        PathP  i  d F (Push→ (push a i))  F (push a i))
              (cong (incₗ F (h₁ (f₁ a))) (con₁ (h₁ (f₁ a)) .snd  f₁ a , refl )
               transportRefl (F (inl (f₁ a))))
              (cong (incᵣ F (h₂ (f₂ a))) (con₂ (h₂ (f₂ a)) .snd  f₂ a , refl )
               transportRefl (F (inr (f₂ a))))
      push-case a i j =
        comp  k  P (doubleCompPath-filler
                         j  inl (e₁ j a))
                        (push (h₀ a))
                         j  inr (e₂ (~ j) a)) (k  j) i) .fst)
              k  λ {(i = i0)  ((cong (incₗ F (e₁ (~ k  ~ j) a))
                                     (con₁ (e₁ (~ k  ~ j) a) .snd
                                       (f₁ a) ,  i  e₁ ((~ k  i)  ~ j) a) ))
                                    λ i  transp  i₁  fst (P (inl (e₁ ((~ k  i₁)  ~ j) a))))
                                             (i  k) (F (inl (f₁ a)))) j
                      ; (i = i1)  ((cong (incᵣ F (e₂ (~ k  ~ j) a))
                                     (con₂ (e₂ (~ k  ~ j) a) .snd
                                        (f₂ a) ,  i  e₂ ((~ k  i)  ~ j) a) ))
                                    λ i  transp  i₁  fst (P (inr (e₂ ((~ k  i₁)  ~ j) a))))
                                             (i  k) (F (inr (f₂ a)))) j
                      ; (j = i0)  lem₂ k i
                      ; (j = i1)  transportRefl (F (push a i)) k})
             (btm₂ i j)
        where
        lem₁ : cong (d F) (push (h₀ a))
              (cong (incₗ F (g₁ (h₀ a)))
                (con₁ (g₁ (h₀ a)) .snd  (f₁ a) , (funExt⁻ e₁ a) )
              push-lem F a
              sym (cong (incᵣ F (g₂ (h₀ a)))
                (con₂ (g₂ (h₀ a)) .snd  (f₂ a) , (funExt⁻ e₂ a) )))
        lem₁ = cong (pushFun F (h₀ a)) (con₀ (h₀ a) .snd  a , refl ∣ₕ)
              recₕ n (a , refl)
              transportRefl _

        lem₂ : SquareP
                 k i  P (doubleCompPath-filler
                              j₁  inl (e₁ j₁ a))
                             (push (h₀ a))
                              j₁  inr (e₂ (~ j₁) a)) k i) .fst)
                (lem₁ i1)
                (cong (d F) ((λ j₁  inl (e₁ j₁ a))
                           ∙∙ push (h₀ a)
                           ∙∙ λ j₁  inr (e₂ (~ j₁) a)))
                 k  d F (inl (e₁ (~ k) a)))
                λ k  d F (inr (e₂ (~ k) a))
        lem₂ = sym lem₁
           λ k i  d F (doubleCompPath-filler
                           j₁  inl (e₁ j₁ a))
                          (push (h₀ a))
                           j₁  inr (e₂ (~ j₁) a)) k i)

        btm : (i j : I)
           P (doubleCompPath-filler
                  j₁  inl (e₁ j₁ a))
                 (push (h₀ a))
                  j₁  inr (e₂ (~ j₁) a)) j i) .fst
        btm i j =
          comp  k  P (doubleCompPath-filler
                            j₁  inl (e₁ j₁ a))
                           (push (h₀ a))
                            j₁  inr (e₂ (~ j₁) a)) (~ k  j) i) .fst)
              k  λ {(i = i0)  transp  i₁  fst (P (inl (e₁ (i₁  (k  ~ j)) a))))
                                           (~ k  ~ j) (F (inl (f₁ a)))
                      ; (i = i1)  transp  i₁  fst (P (inr (e₂ (i₁  (k  ~ j)) a))))
                                           (~ k  ~ j) (F (inr (f₂ a)))
                      ; (j = i1)  transport refl (F (push a i))})
             (transportRefl (F (push a i)) (~ j))

        btm₂ : (i j : I) 
          P (doubleCompPath-filler
              j₂  inl (e₁ j₂ a))
             (push (h₀ a))
              j₂  inr (e₂ (~ j₂) a)) j i) .fst
        btm₂ i j =
          hcomp
            k  λ {(i = i0)  compPath-filler'
                                   (cong (incₗ F (e₁ (~ j) a))
                                    (con₁ (e₁ (~ j) a) .snd
                                       (f₁ a) ,  i  e₁ (i  ~ j) a) ))
                                   refl k j
                      ; (i = i1)  compPath-filler'
                                     (cong (incᵣ F (e₂ (~ j) a))
                                      (con₂ (e₂ (~ j) a) .snd
                                        (f₂ a) ,  i  e₂ (i  ~ j) a) ))
                                     refl k j
                      ; (j = i0)  doubleWhiskFiller
                                      (cong (incₗ F (g₁ (h₀ a)))
                                       (con₁ (g₁ (h₀ a)) .snd
                                         (f₁ a) , (funExt⁻ e₁ a) ))
                                      (push-lem F a)
                                      (sym (cong (incᵣ F (g₂ (h₀ a)))
                                       (con₂ (g₂ (h₀ a)) .snd
                                         (f₂ a) , (funExt⁻ e₂ a) ))) k i
                      ; (j = i1)  transport refl (F (push a i))})
              (btm i j)


module _ {ℓ' ℓ'' : Level}
  (m n : HLevel) {A : Type } {A' : Type ℓ'} {v : A  A'} {B : Type ℓ''}
  (hA : isConnectedFun m v) (hB : isConnected n B) where

  private module _ {ℓ''' : Level} (P : join A' B  TypeOfHLevel ℓ''' (m + n)) where
    module _ (k : (x : join A B)  P (join→ v (idfun B) x) .fst) where
      -- We encode k as a section f of the family
      --   A
      -- v ↓  X
      --   A' → Type
      -- over A, and use the connectivity assumption on v
      -- to extend it to a section f' over A'.

      X : A'  Type _
      X a' =
        Σ[ x  P (inl a') .fst ]
           (b : B)  PathP  i  P (push a' b i) .fst) x (k (inr b))

      f : (a : A)  X (v a)
      fst (f a) = k (inl a)
      snd (f a) = λ b i  k (push a b i)

      -- Equivalent type to X, whose h-level we can estimate.
      X' : A'  Type _
      X' a' =
        Σ[ x'  (Unit  P (inl a') .fst) ]
           (b : B)  x' tt) 
           (b : B)  subst⁻  y  P y .fst) (push a' b) (k (inr b)))

      X≃X' : (a' : A')  X a'  X' a'
      X≃X' a' =
        (Σ[ x  P (inl a') .fst ]
           (b : B)  PathP  i  P (push a' b i) .fst) x (k (inr b)))
        ≃⟨ invEquiv (Σ-cong-equiv-fst (UnitToType≃ _)) 
        (Σ[ x'  (Unit  P (inl a') .fst) ]
           (b : B)  PathP  i  P (push a' b i) .fst) (x' tt) (k (inr b)))
        ≃⟨ Σ-cong-equiv-snd  x'  equivΠCod  b  pathToEquiv (PathP≡Path⁻ _ _ _))) 
        (Σ[ x'  (Unit  P (inl a') .fst) ]
           (b : B)  x' tt  subst⁻  y  P y .fst) (push a' b) (k (inr b)))
        ≃⟨ Σ-cong-equiv-snd  x'  funExtEquiv) 
        (Σ[ x'  (Unit  P (inl a') .fst) ]
           (b : B)  x' tt) 
           (b : B)  subst⁻  y  P y .fst) (push a' b) (k (inr b))))
        

      X'level : (a' : A')  isOfHLevel m (X' a')
      X'level a' =
        isOfHLevelPrecomposeConnected m n
           (_ : Unit)  P (inl a'))  (b : B)  tt)
           _  isConnectedRetractFromIso _ fiberUnitIso hB) _

      Xl : (a' : A')  TypeOfHLevel _ m
      fst (Xl a') = X a'
      snd (Xl a') = isOfHLevelRespectEquiv _ (invEquiv (X≃X' a')) (X'level a')

      H : Iso ((a' : A')  X a') ((a : A)  X (v a))
      H = elim.isIsoPrecompose v _ Xl hA

      f' : (a' : A')  X a'
      f' = Iso.inv H f

      hf' : (a : A)  f' (v a)  f a
      hf' = funExt⁻ (Iso.rightInv H f)

      k' : (x : join A' B)  P x .fst
      k' (inl a') = f' a' .fst
      k' (inr b) = k (inr b)
      k' (push a' b i) = f' a' .snd b i

      hk' : (x : join A B)  k' (join→ v (idfun B) x)  k x
      hk' (inl a) j = hf' a j .fst
      hk' (inr b) j = k (inr b)
      hk' (push a b i) j = hf' a j .snd b i

    joinConnectedAux :
      hasSection  (k : (x : join A' B)  P x .fst)  k  join→ v (idfun B))
    fst joinConnectedAux k = k' k
    snd joinConnectedAux k = funExt (hk' k)

  joinConnected : isConnectedFun (m + n) (join→ v (idfun B))
  joinConnected = elim.isConnectedPrecompose _ _ joinConnectedAux

{- Given two fibration B , C : A → Type and a family of maps on fibres
   f : (a : A) → B a → C a, we have that f a is n-connected for all (a : A)
   iff the induced map on totalspaces Σ A B → Σ A C is n-connected -}
module _ { ℓ' ℓ'' : Level} {A : Type } {B : A  Type ℓ'} {C : A  Type ℓ''}
  (f : ((a : A)  B a  C a))
  where
  open Iso

  TotalFun : Σ A B  Σ A C
  TotalFun (a , b) = a , f a b

  fibTotalFun→fibFun : (x : Σ A C)
     Σ[ y  Σ A B ] TotalFun y  x
     Σ[ y  B (fst x) ] f (fst x) y  snd x
  fibTotalFun→fibFun x =
    uncurry
      λ r  J  x _  Σ[ y  B (fst x) ] f (fst x) y  snd x)
               ((snd r) , refl)

  fibFun→fibTotalFun : (x : Σ A C)
     Σ[ y  B (fst x) ] f (fst x) y  snd x
     Σ[ y  Σ A B ] TotalFun y  x
  fibFun→fibTotalFun x (b , p) = (_ , b) , ΣPathP (refl , p)

  Iso-fibTotalFun-fibFun : (x : Σ A C)
     Iso (Σ[ y  Σ A B ] TotalFun y  x)
           (Σ[ y  B (fst x) ] f (fst x) y  snd x)
  fun (Iso-fibTotalFun-fibFun x) = fibTotalFun→fibFun x
  inv (Iso-fibTotalFun-fibFun x) = fibFun→fibTotalFun x
  rightInv (Iso-fibTotalFun-fibFun x) (r , y) j =
    transp  i  Σ[ b  B (fst x) ] (f (fst x) b  y (i  j))) j
           (r , λ i  y (i  j))
  leftInv (Iso-fibTotalFun-fibFun x) =
    uncurry λ r
       J  x y  inv (Iso-fibTotalFun-fibFun x)
                      (fun (Iso-fibTotalFun-fibFun x) (r , y))
                    (r , y))
           (cong (fibFun→fibTotalFun (TotalFun r))
             (transportRefl (snd r , refl)))

  TotalFunConnected→FunConnected : (n : )
     isConnectedFun n TotalFun
     ((a : A)  isConnectedFun n (f a))
  TotalFunConnected→FunConnected n con a r =
    isConnectedRetractFromIso n
     (invIso (Iso-fibTotalFun-fibFun (a , r))) (con (a , r))

  FunConnected→TotalFunConnected : (n : )
     ((a : A)  isConnectedFun n (f a))
     isConnectedFun n TotalFun
  FunConnected→TotalFunConnected n con r =
    isConnectedRetractFromIso n
     (Iso-fibTotalFun-fibFun r) (con (fst r) (snd r))