{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

module Data.Morpheus.Schema.Schema
  ( withSystemTypes,
    systemTypes,
  )
where

-- MORPHEUS

import Data.Morpheus.Internal.Utils
  ( (<:>),
    singleton,
  )
import Data.Morpheus.Schema.DSL (dsl)
import Data.Morpheus.Types.Internal.AST
  ( ANY,
    DataFingerprint (..),
    FieldsDefinition,
    Message,
    OUT,
    Schema (..),
    TypeContent (..),
    TypeDefinition (..),
    TypeUpdater,
    TypeWrapper (..),
    createArgument,
    insertType,
    internalFingerprint,
    mkField,
    mkObjectField,
    unsafeFromFields,
  )
import Data.Morpheus.Types.Internal.Resolving
  ( failure,
    resolveUpdates,
  )

withSystemTypes :: TypeUpdater
withSystemTypes s@Schema {query = q@TypeDefinition {typeContent = DataObject inter fields}} =
  ( do
      fs <- fields <:> hiddenFields
      pure $ s {query = q {typeContent = DataObject inter fs}}
  )
    >>= (`resolveUpdates` map (insertType . internalType) systemTypes)
withSystemTypes _ = failure ("Query must be an Object Type" :: Message)

hiddenFields :: FieldsDefinition OUT
hiddenFields =
  unsafeFromFields
    [ mkObjectField
        (singleton (createArgument "name" ([], "String")))
        "__type"
        ([TypeMaybe], "__Type"),
      mkField
        "__schema"
        ([], "__Schema")
    ]

internalType :: TypeDefinition a -> TypeDefinition a
internalType
  tyDef@TypeDefinition
    { typeFingerprint = DataFingerprint name xs
    } =
    tyDef {typeFingerprint = internalFingerprint name xs}

systemTypes :: [TypeDefinition ANY]
systemTypes =
  [dsl|

# default scalars
scalar Boolean
scalar Int
scalar Float
scalar String
scalar ID

type __Schema {
  types: [__Type!]!
  queryType: __Type!
  mutationType: __Type
  subscriptionType: __Type
  directives: [__Directive!]!
}

type __Type {
  kind: __TypeKind!
  name: String
  description: String

  # OBJECT and INTERFACE only
  fields(includeDeprecated: Boolean = false): [__Field!]

  # OBJECT only
  interfaces: [__Type!]

  # INTERFACE and UNION only
  possibleTypes: [__Type!]

  # ENUM only
  enumValues(includeDeprecated: Boolean = false): [__EnumValue!]

  # INPUT_OBJECT only
  inputFields: [__InputValue!]

  # NON_NULL and LIST only
  ofType: __Type
}

type __Field {
  name: String!
  description: String
  args: [__InputValue!]!
  type: __Type!
  isDeprecated: Boolean!
  deprecationReason: String
}

type __InputValue {
  name: String!
  description: String
  type: __Type!
  defaultValue: String
}

type __EnumValue {
  name: String!
  description: String
  isDeprecated: Boolean!
  deprecationReason: String
}

type __Directive {
  name: String!
  description: String
  locations: [__DirectiveLocation!]!
  args: [__InputValue!]!
}

enum __DirectiveLocation {
  QUERY
  MUTATION
  SUBSCRIPTION
  FIELD
  FRAGMENT_DEFINITION
  FRAGMENT_SPREAD
  INLINE_FRAGMENT
  SCHEMA
  SCALAR
  OBJECT
  FIELD_DEFINITION
  ARGUMENT_DEFINITION
  INTERFACE
  UNION
  ENUM
  ENUM_VALUE
  INPUT_OBJECT
  INPUT_FIELD_DEFINITION
}

enum __TypeKind {
  SCALAR
  OBJECT
  INTERFACE
  UNION
  ENUM
  INPUT_OBJECT
  LIST
  NON_NULL
}

|]