{-# LANGUAGE DataKinds       #-}
{-# LANGUAGE NamedFieldPuns  #-}
{-# LANGUAGE TemplateHaskell #-}

module Data.Morpheus.Execution.Document.Declare
  ( declareTypes
  )
where

import           Data.Semigroup                 ( (<>) )
import           Language.Haskell.TH


-- MORPHEUS
import           Data.Morpheus.Execution.Document.Decode
                                                ( deriveDecode )
import           Data.Morpheus.Execution.Document.Encode
                                                ( deriveEncode )
import           Data.Morpheus.Execution.Document.GQLType
                                                ( deriveGQLType )
import           Data.Morpheus.Execution.Document.Introspect
                                                ( deriveObjectRep
                                                , instanceIntrospect
                                                )
import           Data.Morpheus.Execution.Internal.Declare
                                                ( declareType )
import           Data.Morpheus.Types.Internal.AST
                                                ( isInput
                                                , isObject
                                                , GQLTypeD(..)
                                                )

declareTypes :: Bool -> [GQLTypeD] -> Q [Dec]
declareTypes namespace = fmap concat . traverse (declareGQLType namespace)

declareGQLType :: Bool -> GQLTypeD -> Q [Dec]
declareGQLType namespace gqlType@GQLTypeD { typeD, typeKindD, typeArgD, typeOriginal }
  = do
    mainType       <- declareMainType
    argTypes       <- declareArgTypes
    gqlInstances   <- deriveGQLInstances
    typeClasses    <- deriveGQLType gqlType
    introspectEnum <- instanceIntrospect typeOriginal
    pure $ mainType <> typeClasses <> argTypes <> gqlInstances <> introspectEnum
 where
  deriveGQLInstances = concat <$> sequence gqlInstances
   where
    gqlInstances
      | isObject typeKindD && isInput typeKindD
      = [deriveObjectRep (typeD, Nothing), deriveDecode typeD]
      | isObject typeKindD
      = [deriveObjectRep (typeD, Just typeKindD), deriveEncode gqlType]
      | otherwise
      = []
  --------------------------------------------------
  declareArgTypes = do
    introspectArgs <- concat <$> traverse deriveArgsRep typeArgD
    decodeArgs     <- concat <$> traverse deriveDecode typeArgD
    return $ argsTypeDecs <> decodeArgs <> introspectArgs
   where
    deriveArgsRep args = deriveObjectRep (args, Nothing)
    ----------------------------------------------------
    argsTypeDecs = map (declareType namespace Nothing []) typeArgD
      --------------------------------------------------
  declareMainType = declareT
   where
    declareT =
      pure [declareType namespace (Just typeKindD) derivingClasses typeD]
    derivingClasses | isInput typeKindD = [''Show]
                    | otherwise         = []