# LANGUAGE UndecidableInstances #
# LANGUAGE UndecidableSuperClasses #

module Ema.Route.Generic.RGeneric (
  RGeneric (RCode, rfrom, rto),
  RHasDatatypeInfo (RDatatypeName, RConstructorNames),
) where

import GHC.TypeLits (ErrorMessage (Text), TypeError)
import GHC.TypeLits.Extra (Impossible (impossible), TypeErr)
import Generics.SOP
import Generics.SOP.Type.Metadata qualified as SOPM
import Prelude hiding (All, Generic)

  | Like ` Generic ` but for Route types only .
class (Generic r, HasDatatypeInfo r) => RGeneric r where
  type RCode r :: [Type]
  rfrom :: r -> NS I (RCode r)
  rto :: NS I (RCode r) -> r

class (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
  type RDatatypeName r :: SOPM.DatatypeName
  type RConstructorNames r :: [SOPM.ConstructorName]

instance (Generic r, HasDatatypeInfo r, RGeneric' (Code r), All RouteNP (Code r)) => RGeneric r where
  type RCode r = RCode' (Code r)
  rfrom = rfrom' @(Code r) . unSOP . from
  rto = to . SOP . rto' @(Code r)

instance (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
  type RDatatypeName r = RDatatypeName' (DatatypeInfoOf r)
  type RConstructorNames r = RConstructorNames' (DatatypeInfoOf r)

class RGeneric' (xss :: [[Type]]) where
  type RCode' xss :: [Type]
  rfrom' :: NS (NP I) xss -> NS I (RCode' xss)
  rto' :: NS I (RCode' xss) -> NS (NP I) xss

instance RGeneric' '[] where
  type RCode' '[] = '[]
  rfrom' = \case {}
  rto' = \case {}

instance (RGeneric' xss, RouteNP xs) => RGeneric' (xs ': xss) where
  type RCode' (xs ': xss) = RouteNPType xs ': RCode' xss
  rfrom' = \case
    Z routeNP -> Z $ I $ fromRouteNP routeNP
    S rest -> S $ rfrom' @xss rest
  rto' = \case
    Z (I t) -> Z $ toRouteNP t
    S rest -> S $ rto' @xss rest

type family RDatatypeName' (info :: SOPM.DatatypeInfo) :: SOPM.DatatypeName where
  RDatatypeName' ('SOPM.ADT _ name _ _) =
    name
  RDatatypeName' ('SOPM.Newtype _ name _) =
    name

type family RConstructorNames' (info :: SOPM.DatatypeInfo) :: [SOPM.ConstructorName] where
  RConstructorNames' ('SOPM.ADT _ _ constrs _) =
    GetConstructorNames constrs
  RConstructorNames' ('SOPM.Newtype _ _ constr) =
    '[GetConstructorName constr]

type family GetConstructorName (info :: SOPM.ConstructorInfo) :: SOPM.ConstructorName where
  GetConstructorName ('SOPM.Constructor name) =
    name
  GetConstructorName ('SOPM.Infix name _ _) =
    name
  GetConstructorName ('SOPM.Record name _) =
    name

type family GetConstructorNames (infos :: [SOPM.ConstructorInfo]) :: [SOPM.ConstructorName] where
  GetConstructorNames '[] = '[]
  GetConstructorNames (x ': xs) = GetConstructorName x ': GetConstructorNames xs

  | Class of ` NP ` that can be used in a route tyupe . 

     Ensures that the constructor can have 0 or 1 products only . 


    Ensures that the constructor can have 0 or 1 products only.
-}
class RouteNP (xs :: [Type]) where
  type RouteNPType xs :: Type
  fromRouteNP :: NP I xs -> RouteNPType xs
  toRouteNP :: RouteNPType xs -> NP I xs

instance RouteNP '[] where
  type RouteNPType '[] = ()
  fromRouteNP Nil = ()
  toRouteNP () = Nil

instance RouteNP (x ': '[]) where
  type RouteNPType (x ': '[]) = x
  fromRouteNP (I x :* Nil) = x
  toRouteNP x = I x :* Nil

instance (TypeErr ('Text "MultiRoute: too many arguments")) => RouteNP (x ': x' ': xs) where
  type RouteNPType (x ': x' ': xs) = TypeError ('Text "MultiRoute: too many arguments")
  fromRouteNP _ = impossible
  toRouteNP _ = impossible

# LANGUAGE UndecidableInstances #
# LANGUAGE UndecidableSuperClasses #

module Ema.Route.Generic.RGeneric (
  RGeneric (RCode, rfrom, rto),
  RHasDatatypeInfo (RDatatypeName, RConstructorNames),
) where

import GHC.TypeLits (ErrorMessage (Text), TypeError)
import GHC.TypeLits.Extra (Impossible (impossible), TypeErr)
import Generics.SOP
import Generics.SOP.Type.Metadata qualified as SOPM
import Prelude hiding (All, Generic)

  | Like ` Generic ` but for Route types only .
class (Generic r, HasDatatypeInfo r) => RGeneric r where
  type RCode r :: [Type]
  rfrom :: r -> NS I (RCode r)
  rto :: NS I (RCode r) -> r

class (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
  type RDatatypeName r :: SOPM.DatatypeName
  type RConstructorNames r :: [SOPM.ConstructorName]

instance (Generic r, HasDatatypeInfo r, RGeneric' (Code r), All RouteNP (Code r)) => RGeneric r where
  type RCode r = RCode' (Code r)
  rfrom = rfrom' @(Code r) . unSOP . from
  rto = to . SOP . rto' @(Code r)

instance (RGeneric r, HasDatatypeInfo r) => RHasDatatypeInfo r where
  type RDatatypeName r = RDatatypeName' (DatatypeInfoOf r)
  type RConstructorNames r = RConstructorNames' (DatatypeInfoOf r)

class RGeneric' (xss :: [[Type]]) where
  type RCode' xss :: [Type]
  rfrom' :: NS (NP I) xss -> NS I (RCode' xss)
  rto' :: NS I (RCode' xss) -> NS (NP I) xss

instance RGeneric' '[] where
  type RCode' '[] = '[]
  rfrom' = \case {}
  rto' = \case {}

instance (RGeneric' xss, RouteNP xs) => RGeneric' (xs ': xss) where
  type RCode' (xs ': xss) = RouteNPType xs ': RCode' xss
  rfrom' = \case
    Z routeNP -> Z $ I $ fromRouteNP routeNP
    S rest -> S $ rfrom' @xss rest
  rto' = \case
    Z (I t) -> Z $ toRouteNP t
    S rest -> S $ rto' @xss rest

type family RDatatypeName' (info :: SOPM.DatatypeInfo) :: SOPM.DatatypeName where
  RDatatypeName' ('SOPM.ADT _ name _ _) =
    name
  RDatatypeName' ('SOPM.Newtype _ name _) =
    name

type family RConstructorNames' (info :: SOPM.DatatypeInfo) :: [SOPM.ConstructorName] where
  RConstructorNames' ('SOPM.ADT _ _ constrs _) =
    GetConstructorNames constrs
  RConstructorNames' ('SOPM.Newtype _ _ constr) =
    '[GetConstructorName constr]

type family GetConstructorName (info :: SOPM.ConstructorInfo) :: SOPM.ConstructorName where
  GetConstructorName ('SOPM.Constructor name) =
    name
  GetConstructorName ('SOPM.Infix name _ _) =
    name
  GetConstructorName ('SOPM.Record name _) =
    name

type family GetConstructorNames (infos :: [SOPM.ConstructorInfo]) :: [SOPM.ConstructorName] where
  GetConstructorNames '[] = '[]
  GetConstructorNames (x ': xs) = GetConstructorName x ': GetConstructorNames xs

  | Class of ` NP ` that can be used in a route tyupe . 

     Ensures that the constructor can have 0 or 1 products only . 


    Ensures that the constructor can have 0 or 1 products only.
-}
class RouteNP (xs :: [Type]) where
  type RouteNPType xs :: Type
  fromRouteNP :: NP I xs -> RouteNPType xs
  toRouteNP :: RouteNPType xs -> NP I xs

instance RouteNP '[] where
  type RouteNPType '[] = ()
  fromRouteNP Nil = ()
  toRouteNP () = Nil

instance RouteNP (x ': '[]) where
  type RouteNPType (x ': '[]) = x
  fromRouteNP (I x :* Nil) = x
  toRouteNP x = I x :* Nil

instance (TypeErr ('Text "MultiRoute: too many arguments")) => RouteNP (x ': x' ': xs) where
  type RouteNPType (x ': x' ': xs) = TypeError ('Text "MultiRoute: too many arguments")
  fromRouteNP _ = impossible
  toRouteNP _ = impossible
