{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators       #-}

module Data.Morpheus.Validation.Query.Validation
  ( validateRequest
  ) where

import           Data.Map                                   (fromList)
import           Data.Morpheus.Error.Mutation               (mutationIsNotDefined)
import           Data.Morpheus.Error.Subscription           (subscriptionIsNotDefined)
import           Data.Morpheus.Types.Internal.AST.Operation (Operation (..), RawOperation, ValidOperation)
import           Data.Morpheus.Types.Internal.Data          (DataObject, DataTypeLib (..), OperationKind (..))
import           Data.Morpheus.Types.Internal.Validation    (Validation)
import           Data.Morpheus.Types.Types                  (GQLQueryRoot (..))
import           Data.Morpheus.Validation.Internal.Utils    (VALIDATION_MODE)
import           Data.Morpheus.Validation.Query.Fragment    (validateFragments)
import           Data.Morpheus.Validation.Query.Selection   (validateSelectionSet)
import           Data.Morpheus.Validation.Query.Variable    (resolveOperationVariables)

getOperationDataType :: RawOperation -> DataTypeLib -> Validation DataObject
getOperationDataType Operation {operationKind = Query} lib = pure $ snd $ query lib
getOperationDataType Operation {operationKind = Mutation, operationPosition} lib =
  case mutation lib of
    Just (_, mutation') -> pure mutation'
    Nothing             -> Left $ mutationIsNotDefined operationPosition
getOperationDataType Operation {operationKind = Subscription, operationPosition} lib =
  case subscription lib of
    Just (_, subscription') -> pure subscription'
    Nothing                 -> Left $ subscriptionIsNotDefined operationPosition

validateRequest :: DataTypeLib -> VALIDATION_MODE -> GQLQueryRoot -> Validation ValidOperation
validateRequest lib validationMode GQLQueryRoot { fragments
                                                , inputVariables
                                                , operation = rawOperation@Operation { operationName
                                                                                     , operationKind
                                                                                     , operationSelection
                                                                                     , operationPosition
                                                                                     }
                                                } = do
  operationDataType <- getOperationDataType rawOperation lib
  variables <- resolveOperationVariables lib fragments (fromList inputVariables) validationMode rawOperation
  validateFragments lib fragments operationSelection
  selection <- validateSelectionSet lib fragments operationName variables operationDataType operationSelection
  pure $ Operation {operationName, operationKind, operationArgs = [], operationSelection = selection, operationPosition}