{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators     #-}

module Data.Morpheus.Parsing.JSONSchema.Parse
  ( decodeIntrospection
  ) where

import           Data.Aeson
import           Data.ByteString.Lazy                    (ByteString)
import           Data.Morpheus.Error.Internal            (internalError)
import           Data.Morpheus.Parsing.Internal.Create   (createArgument, createDataTypeLib, createEnumType,
                                                          createField, createScalarType, createType, createUnionType)
import qualified Data.Morpheus.Schema.EnumValue          as E (EnumValue (..))
import qualified Data.Morpheus.Schema.Field              as F (Field (..))
import qualified Data.Morpheus.Schema.InputValue         as I (InputValue (..))
import           Data.Morpheus.Schema.JSONType           (JSONIntro (..), JSONSchema (..), JSONType (..))
import           Data.Morpheus.Schema.TypeKind           (TypeKind (..))
import           Data.Morpheus.Types.Internal.Data       (DataFullType (..), DataTypeLib, DataTypeWrapper (..))
import           Data.Morpheus.Types.Internal.Validation (Validation)
import           Data.Morpheus.Types.IO                  (JSONResponse (..))
import           Data.Semigroup                          ((<>))
import           Data.Text                               (Text, pack)

typeFromJSON :: JSONType -> Validation (Text, DataFullType)
typeFromJSON JSONType {name = Just typeName, kind = SCALAR} = pure $ createScalarType typeName
typeFromJSON JSONType {name = Just typeName, kind = ENUM, enumValues = Just enums} =
  pure $ createEnumType typeName (map E.name enums)
typeFromJSON JSONType {name = Just typeName, kind = UNION, possibleTypes = Just unions} =
  case traverse name unions of
    Nothing  -> fail "ERROR: GQL ERROR"
    Just uni -> pure $ createUnionType typeName uni
typeFromJSON JSONType {name = Just typeName, kind = INPUT_OBJECT, inputFields = Just iFields} = do
  fields <- traverse iField iFields
  pure (typeName, InputObject $ createType typeName fields)
  where
    iField I.InputValue {I.name = fieldName, I.type' = fType} = do
      fieldType <- fieldTypeFromJSON fType
      pure (fieldName, createField () fieldName fieldType)
typeFromJSON JSONType {name = Just typeName, kind = OBJECT, fields = Just oFields} = do
  fields <- traverse oField oFields
  pure (typeName, OutputObject $ createType typeName fields)
  where
    oField F.Field {F.name = fieldName, F.args = fArgs, F.type' = fType} = do
      fieldType <- fieldTypeFromJSON fType
      args <- traverse genArg fArgs
      pure (fieldName, createField args fieldName fieldType)
      where
        genArg I.InputValue {I.name = argName, I.type' = argType} = createArgument argName <$> fieldTypeFromJSON argType
typeFromJSON x = internalError $ "Unsuported type" <> pack (show x)

fieldTypeFromJSON :: JSONType -> Validation ([DataTypeWrapper], Text)
fieldTypeFromJSON = fieldTypeRec []
  where
    fieldTypeRec :: [DataTypeWrapper] -> JSONType -> Validation ([DataTypeWrapper], Text)
    fieldTypeRec acc JSONType {kind = LIST, ofType = Just ofType} = fieldTypeRec (ListType : acc) ofType
    fieldTypeRec acc JSONType {kind = NON_NULL, ofType = Just ofType} = fieldTypeRec (NonNullType : acc) ofType
    fieldTypeRec acc JSONType {name = Just name} = pure (acc, name)
    fieldTypeRec _ x = internalError $ "Unsuported Field" <> pack (show x)

schemaFromJSON :: [JSONType] -> Validation [(Text, DataFullType)]
schemaFromJSON = traverse typeFromJSON

decodeIntrospection :: ByteString -> Validation DataTypeLib
decodeIntrospection jsonDoc =
  case jsonSchema of
    Left errors -> internalError $ pack errors
    Right JSONResponse {responseData = Just JSONIntro {__schema = JSONSchema {types}}} ->
      schemaFromJSON types >>= createDataTypeLib
    Right res -> fail $ show res
  where
    jsonSchema :: Either String (JSONResponse JSONIntro)
    jsonSchema = eitherDecode jsonDoc