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

module Data.Morpheus.Validation.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 (..), OperationKind (..), RawOperation,
                                                             ValidOperation)
import           Data.Morpheus.Types.Internal.Data          (DataOutputObject, DataTypeLib (..))
import           Data.Morpheus.Types.Internal.Validation    (Validation)
import           Data.Morpheus.Types.Types                  (GQLQueryRoot (..))
import           Data.Morpheus.Validation.Fragment          (validateFragments)
import           Data.Morpheus.Validation.Selection         (validateSelectionSet)
import           Data.Morpheus.Validation.Utils.Utils       (VALIDATION_MODE)
import           Data.Morpheus.Validation.Variable          (resolveOperationVariables)

getOperationDataType ::
     RawOperation -> DataTypeLib -> Validation DataOutputObject
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
      }