{-# LANGUAGE UndecidableInstances #-} -- due to typeclass design
{-# LANGUAGE AllowAmbiguousTypes #-}  -- due to typeclass design

module Generic.Data.Function.Contra.Sum where

import GHC.Generics
import Generic.Data.Function.Util.Generic ( conName' )
import Generic.Data.Function.Contra.Constructor
  ( GContraC(gContraC)
  , GenericContra(type GenericContraF)
  )
import Generic.Data.Rep.Error
import Generic.Data.Function.Common

import Data.Functor.Contravariant.Divisible
import Data.Functor.Contravariant

class GContraSum tag (opts :: SumOpts) gf where
    gContraSum :: GenericContraF tag String -> GenericContraF tag (gf p)

instance (GContraSumD tag opts gf, Contravariant (GenericContraF tag))
  => GContraSum tag opts (D1 cd gf) where
    gContraSum f = contramap unM1 (gContraSumD @tag @opts f)

class GContraSumD tag (opts :: SumOpts) gf where
    gContraSumD :: GenericContraF tag String -> GenericContraF tag (gf p)

instance GContraCSum tag (l :+: r) => GContraSumD tag opts (l :+: r) where
    gContraSumD = gContraCSum @tag

instance GContraSumD tag 'SumOnly (C1 cc gf) where
    gContraSumD = error eNeedSum

instance GContraCSum tag (C1 cc gf)
  => GContraSumD tag 'AllowSingletonSum (C1 cc gf) where
    gContraSumD = gContraCSum @tag

instance GContraSumD tag opts V1 where
    gContraSumD = error eNoEmpty

-- TODO rename (? had this on foldmap sum)
class GContraCSum tag gf where
    gContraCSum :: GenericContraF tag String -> GenericContraF tag (gf p)

instance
  ( Decidable (GenericContraF tag)
  , GContraCSum tag l
  , GContraCSum tag r
  ) => GContraCSum tag (l :+: r) where
    gContraCSum f = choose genericSumToEither (gContraCSum @tag f) (gContraCSum @tag f)
      where genericSumToEither = \case L1 l -> Left l; R1 r -> Right r

instance
  ( Divisible (GenericContraF tag)
  , GContraC tag gf, Constructor c
  ) => GContraCSum tag (C1 c gf) where
    gContraCSum f = divide (\(M1 g) -> (conName' @c, g)) f (gContraC @tag)