content: {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE NamedFieldPuns #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  
  module Data.Morpheus.Client.CodeGen.Interpreting.Global
    ( toGlobalDefinitions,
    )
  where
  
  import Data.Morpheus.Client.CodeGen.AST
    ( ClientDeclaration,
      ClientPreDeclaration (..),
      ClientTypeDefinition (..),
      DERIVING_MODE (..),
    )
  import Data.Morpheus.Client.CodeGen.Interpreting.Core (printClientType)
  import Data.Morpheus.Client.CodeGen.Interpreting.PreDeclarations
    ( mapPreDeclarations,
    )
  import Data.Morpheus.CodeGen.Internal.AST
    ( CodeGenConstructor (..),
      CodeGenField (..),
      CodeGenTypeName (..),
      FIELD_TYPE_WRAPPER (..),
      fromTypeName,
    )
  import Data.Morpheus.CodeGen.Utils (Flag (FlagLanguageExtension), Flags)
  import Data.Morpheus.Internal.Ext (GQLResult)
  import Data.Morpheus.Types.Internal.AST
    ( ANY,
      DataEnumValue (DataEnumValue, enumName),
      FieldDefinition (..),
      Schema (Schema, types),
      TRUE,
      TypeContent (..),
      TypeDefinition (..),
      TypeKind (..),
      TypeName,
      TypeRef (..),
      VALID,
      isNotSystemTypeName,
      isNullable,
      isResolverType,
    )
  import Relude hiding (empty)
  
  toClientDeclarations :: ClientTypeDefinition -> [ClientPreDeclaration]
  toClientDeclarations def@ClientTypeDefinition {clientKind}
    | KIND_SCALAR == clientKind = [FromJSONClass SCALAR_MODE cgType, ToJSONClass SCALAR_MODE cgType]
    | KIND_ENUM == clientKind = [ClientType cgType, FromJSONClass ENUM_MODE cgType, ToJSONClass ENUM_MODE cgType]
    | otherwise = [ClientType cgType, ToJSONClass TYPE_MODE cgType]
    where
      cgType = printClientType def
  
  toGlobalDefinitions :: (TypeName -> Bool) -> Schema VALID -> GQLResult ([ClientDeclaration], Flags)
  toGlobalDefinitions f Schema {types} = do
    let tyDefs = mapMaybe generateGlobalType $ filter shouldInclude (sortWith typeName $ toList types)
    decs <- traverse mapPreDeclarations $ concatMap toClientDeclarations tyDefs
    let hasEnums = not $ null [x | x@ClientTypeDefinition {clientKind = KIND_ENUM} <- tyDefs]
    pure (decs, [FlagLanguageExtension "LambdaCase" | hasEnums])
    where
      shouldInclude t =
        not (isResolverType t)
          && isNotSystemTypeName (typeName t)
          && f (typeName t)
  
  generateGlobalType :: TypeDefinition ANY VALID -> Maybe ClientTypeDefinition
  generateGlobalType TypeDefinition {typeName, typeContent} = do
    (clientKind, clientCons) <- genContent typeContent
    pure
      ClientTypeDefinition
        { clientTypeName = fromTypeName typeName,
          clientKind,
          clientCons
        }
    where
      genContent :: TypeContent TRUE ANY VALID -> Maybe (TypeKind, [CodeGenConstructor])
      genContent (DataInputObject inputFields) =
        pure
          ( KIND_INPUT_OBJECT,
            [ CodeGenConstructor
                { constructorName = fromTypeName typeName,
                  constructorFields = toCodeGenField <$> toList inputFields
                }
            ]
          )
      genContent (DataEnum enumTags) = pure (KIND_ENUM, mkConsEnum typeName <$> enumTags)
      genContent DataScalar {} = pure (KIND_SCALAR, [])
      genContent _ = Nothing
  
  toCodeGenField :: FieldDefinition a b -> CodeGenField
  toCodeGenField FieldDefinition {fieldType = field@TypeRef {..}, ..} =
    CodeGenField
      { fieldName,
        fieldType = typeConName,
        wrappers = [GQL_WRAPPER typeWrappers],
        fieldIsNullable = isNullable field
      }
  
  mkConsEnum :: TypeName -> DataEnumValue s -> CodeGenConstructor
  mkConsEnum typename DataEnumValue {enumName} =
    CodeGenConstructor (CodeGenTypeName [coerce typename] [] enumName) []
  