{-# OPTIONS --without-K --cubical-compatible  #-}

open import common
open import palg
open import inductive-repair.indspec
open import inductive-repair.config
open import inductive-repair.palg-config
open import inductive-repair.wtype-config hiding (A ; B)
open import inductive-repair.alg-iso
open import inductive-repair.bridge
open import inductive-repair.conversion

{-
   Target theorem (`wtypeConfigEqv`, bottom of this file):

       WTypeConfig A B C D
         ≃
       Σ[ supC ] Σ[ supD ]
         (AlgEquiv (C , supC) (D , supD) × isInitAlg (C , supC))

   — a `Config` over the W-type signature is the same data as
   structure maps `supC`, `supD`, an algebra equivalence between
   them, and initiality of the source. Reduction chain: record η
   on `Config`, the carrier bridge of `inductive-repair.bridge`
   per side, record η on `AlgConfig`, and the algebra-level
   `fullAlgIso` below.
-}

module inductive-repair.config-alg-equiv where

   {-
        The underlying type equivalence of a `palg` algebra
        equivalence: keep the carrier maps, derive the round-trips
        by projecting the bi-invertibility equations to the
        carrier (`ap fst`, then `happly`).
    -}
    algEquivToTypeEquiv : {l : Level} {A : Type l} {B : A  Type l}
                          {X Y : P-Alg {ℓ₃ = l} A B} 
                          AlgEquiv X Y  Carrier X  Carrier Y
    algEquivToTypeEquiv (fwd , (ginv , ginv-eq) , (hinv , hinv-eq)) = record
      { f = fwd  .fst
      ; g = ginv .fst
      ; η = happly (ap fst ginv-eq)
      ; h = hinv .fst
      ; ε = happly (ap fst hinv-eq)
      }

    {-
        The `PAlgConfig` a `Config` converts to — named so it can
        appear in the type of `configToAlgIso` below.
    -}
    cfgPAlg : {l : Level} {C D : Type l} {sig : Signature {l}} 
              Config sig C D  PAlgConfig C D
    cfgPAlg {sig = sig} cfg = configToPalg (record { sig = sig ; config = cfg })

    {-
       The correspondence at the `Config sig C D` level: a
       configuration is exactly an algebra isomorphism together
       with initiality of the source (structure maps determined
       by the config). The two underlying maps follow.
    -}

    {-
        Forward (upgrade of `configToEquiv`): convert to
        P-algebras, then read off the algebra equivalence and the
        source's initiality.
    -}
    configToAlgIso :
        {l : Level} {C D : Type l} {sig : Signature {l}} (cfg : Config sig C D) 
        AlgEquiv (C , InitAlgOn.sup (AlgConfig.initAlgC (PAlgConfig.algConfig (cfgPAlg cfg))))
                 (D , InitAlgOn.sup (AlgConfig.initAlgD (PAlgConfig.algConfig (cfgPAlg cfg))))
          × isInitAlg (C , InitAlgOn.sup (AlgConfig.initAlgC (PAlgConfig.algConfig (cfgPAlg cfg))))
    configToAlgIso cfg =
        algConfigToAlgEquiv A B algConfig , InitAlgOn.isInit (AlgConfig.initAlgC algConfig)
      where open PAlgConfig (cfgPAlg cfg)

    {-
        Reverse (upgrade of `equivToConfig`): the source's
        initiality gives an `IndAlg` on `C` via
        `initToHalf.initAlgToIndAlg`, and `equivToConfig`
        transfers it to `D` along the carrier equivalence.
    -}
    algIsoToConfig :
        {l : Level} {C D : Type l} {A : Type l} {B : A  Type l}
        {supC : P A B C  C} {supD : P A B D  D} 
        AlgEquiv (C , supC) (D , supD) × isInitAlg (C , supC) 
        Config (WTypeSignature {l} A B) C D
    algIsoToConfig {C = C} {A = A} {B} {supC} (e , isInitC) =
        equivToConfig (initToHalf.initAlgToIndAlg A B initC) (algEquivToTypeEquiv e)
      where
        initC : InitAlgOn A B C
        initC = record { sup = supC ; isInit = isInitC }

    {-
       The algebra-level equivalence

            AlgConfig A B C D  ≃  Σ supC Σ supD
                                    (AlgEquiv (C,supC) (D,supD)
                                       × isInitAlg (C,supC))

       An `AlgConfig` is *exactly* a pair of structure maps
       together with an algebra isomorphism between them and
       initiality of the source — the structure maps are data, not
       existentials. Both round-trips are immediate because
       `isInitAlg` is a proposition (`isProp-isInitAlg`) and
       `AlgEquiv` out of an initial source is a proposition
       (`isProp-AlgEquiv`). The two maps reuse `algConfigToAlgEquiv`
       (forward) and `initTransfer` (which manufactures the target's
       initiality from the isomorphism).
    -}

    AlgInitEquiv : {l : Level} (A : Type l) (B : A  Type l) (C D : Type l) 
                 Type (lsuc l)
    AlgInitEquiv A B C D =
      Σ (P A B C  C) λ supC 
      Σ (P A B D  D) λ supD 
      AlgEquiv (C , supC) (D , supD) × isInitAlg (C , supC)

    {- The four components of `fullAlgIso` are pulled out as
       top-level definitions in a private parameterised module so
       the round-trip proofs `fai-to-from` / `fai-from-to` can sit
       inside an `abstract` block. Same shape as the
       `wIsInit-uniq` seal in `palg-config`: the *types* of the
       round-trips stay literal (`fai-from (fai-to cfg) ≡ cfg` etc.),
       but their bodies — which expand `isProp-isInitAlg` /
       `isProp-AlgEquiv` against substituted record literals — do
       not unfold when `to-from'` / `from-to'` below are elaborated. -}
    private
      module FAI {l : Level} {A : Type l} {B : A  Type l} {C D : Type l} where

        fai-to : AlgConfig A B C D  AlgInitEquiv A B C D
        fai-to cfg =
            InitAlgOn.sup initAlgC
          , InitAlgOn.sup initAlgD
          , (algConfigToAlgEquiv A B cfg , InitAlgOn.isInit initAlgC)
          where open AlgConfig cfg

        fai-from : AlgInitEquiv A B C D  AlgConfig A B C D
        fai-from (supC , supD , (e , ic)) = record
          { initAlgC = record { sup = supC ; isInit = ic }
          ; initAlgD = record { sup = supD ; isInit = initTransfer e ic } }

        abstract
          fai-to-from : (cfg : AlgConfig A B C D)  fai-from (fai-to cfg)  cfg
          fai-to-from cfg =
              ap  z  record
                    { initAlgC = AlgConfig.initAlgC cfg
                    ; initAlgD = record
                        { sup = InitAlgOn.sup (AlgConfig.initAlgD cfg)
                        ; isInit = z } })
                 (isProp-isInitAlg (D , InitAlgOn.sup (AlgConfig.initAlgD cfg))
                    (initTransfer (algConfigToAlgEquiv A B cfg)
                                  (InitAlgOn.isInit (AlgConfig.initAlgC cfg)))
                    (InitAlgOn.isInit (AlgConfig.initAlgD cfg)))

          fai-from-to : (y : AlgInitEquiv A B C D)  fai-to (fai-from y)  y
          fai-from-to (supC , supD , (e , ic)) =
              ap  z  supC , supD , (z , ic))
                 (isProp-AlgEquiv ic
                    (algConfigToAlgEquiv A B (fai-from (supC , supD , (e , ic))))
                    e)

    fullAlgIso : {l : Level} {A : Type l} {B : A  Type l} {C D : Type l} 
                 AlgConfig A B C D  AlgInitEquiv A B C D
    fullAlgIso = record
      { f = FAI.fai-to     ; g = FAI.fai-from
      ; η = FAI.fai-to-from ; h = FAI.fai-from
      ; ε = FAI.fai-from-to }

    {-
       From `Config` to the algebra-isomorphism data and back

       Composing the (already-defined) `WTypeConfig → AlgConfig`
       conversion `wToAlg` with the equivalence `fullAlgIso` gives a
       total map `WTypeConfig A B C D → AlgInitEquiv A B C D`, and the
       reverse `AlgConfig → WTypeConfig` (build an `IndAlg` on each
       carrier from its initial-algebra structure via
       `initToHalf.initAlgToIndAlg`) composed with `fullAlgIso`'s
       inverse gives the map back. These are the two halves of the
       target correspondence at the `Config` (not `AlgConfig`) level.
    -}

    wConfigToAlgInitEquiv :
        {l : Level} {A : Type l} {B : A  Type l} {C D : Type l} 
        WTypeConfig {l} A B C D  AlgInitEquiv A B C D
    wConfigToAlgInitEquiv {A = A} {B} cfg = _≃_.f fullAlgIso (wToAlg A B cfg)

    algInitEquivToWConfig :
        {l : Level} {A : Type l} {B : A  Type l} {C D : Type l} 
        AlgInitEquiv A B C D  WTypeConfig {l} A B C D
    algInitEquivToWConfig {A = A} {B} d = record
      { indAlgC = initToHalf.initAlgToIndAlg A B (AlgConfig.initAlgC acfg)
      ; indAlgD = initToHalf.initAlgToIndAlg A B (AlgConfig.initAlgD acfg) }
      where acfg = _≃_.g fullAlgIso d

    {- The carrier-level bridge `WTypeIndAlg A B C ≃ InitAlgOn A B C`
       (and all the supporting `bridgeη-beta` machinery) lives in
       `inductive-repair.bridge`. It is opened above; we use its
       `bridgeFwd` / `bridgeBwd` / `bridgeε` / `bridgeη` / `bridgeEqv`
       directly below. -}

    {-
       The target theorem: WTypeConfig ≃ AlgInitEquiv

       Composes the carrier-level bridge equivalence (on each of `C`
       and `D`) with `fullAlgIso`. Equivalently: `Config` records and
       `AlgConfig` records each have two carrier-level fields, so the
       carrier bridge lifts to a `WTypeConfig ≃ AlgConfig`, and then
       `fullAlgIso` gives the rest.
    -}

    module _ {l : Level} {A : Type l} {B : A  Type l} {C D : Type l} where

      private
        mkW : WTypeIndAlg {l} A B C  WTypeIndAlg {l} A B D  WTypeConfig {l} A B C D
        mkW iC iD = record { indAlgC = iC ; indAlgD = iD }

        mkWFromAlg : AlgConfig A B C D  WTypeConfig {l} A B C D
        mkWFromAlg acfg = mkW (bridgeBwd (AlgConfig.initAlgC acfg))
                              (bridgeBwd (AlgConfig.initAlgD acfg))

        mkA : InitAlgOn A B C  InitAlgOn A B D  AlgConfig A B C D
        mkA pC pD = record { initAlgC = pC ; initAlgD = pD }

        to' : WTypeConfig {l} A B C D  AlgInitEquiv A B C D
        to' = wConfigToAlgInitEquiv

        from' : AlgInitEquiv A B C D  WTypeConfig {l} A B C D
        from' = algInitEquivToWConfig

        {- `from ∘ to`: first collapse `fullAlgIso.g ∘ fullAlgIso.f` via
           `fullAlgIso.η`, then discharge each component by `bridgeη`. -}
        to-from' : (cfg : WTypeConfig {l} A B C D)  from' (to' cfg)  cfg
        to-from' cfg =
            ap mkWFromAlg (_≃_.η fullAlgIso (wToAlg A B cfg))
           ap frame-C (bridgeη iC)
           ap (mkW iC) (bridgeη iD)
          where
            iC = Config.indAlgC cfg
            iD = Config.indAlgD cfg
            frame-C = λ z  mkW z (bridgeBwd (bridgeFwd iD))

        {- `to ∘ from`: factors through `fullAlgIso`'s round-trip
           and `bridgeε` on each carrier. -}
        from-to' : (d : AlgInitEquiv A B C D)  to' (from' d)  d
        from-to' d =
            ap (_≃_.f fullAlgIso)
               (ap frame-C (bridgeε pC)  ap (mkA pC) (bridgeε pD))
           _≃_.ε fullAlgIso d
          where
            acfg = _≃_.g fullAlgIso d
            pC = AlgConfig.initAlgC acfg
            pD = AlgConfig.initAlgD acfg
            frame-C = λ z  mkA z (bridgeFwd (bridgeBwd pD))

      wtypeConfigEqv : WTypeConfig {l} A B C D  AlgInitEquiv A B C D
      wtypeConfigEqv = record
        { f = to' ; g = from' ; η = to-from' ; h = from' ; ε = from-to' }