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

module Data.Morpheus.PreProcess.PreProcess
  ( preProcessQuery
  ) where

import           Data.Map                                         (fromList)
import           Data.Morpheus.Error.Mutation                     (mutationIsNotDefined)
import           Data.Morpheus.PreProcess.Fragment                (validateFragments)
import           Data.Morpheus.PreProcess.Resolve.ResolveRawQuery (resolveRawQuery)
import           Data.Morpheus.PreProcess.Validate.Validate       (mapSelectorValidation)
import           Data.Morpheus.PreProcess.Variable                (allVariableReferences, resolveOperationVariables)
import           Data.Morpheus.Schema.Internal.Types              (GObject (..), ObjectField (..), OutputObject,
                                                                   TypeLib (..))
import qualified Data.Morpheus.Schema.Internal.Types              as SC (Field (..))
import           Data.Morpheus.Schema.TypeKind                    (TypeKind (..))
import           Data.Morpheus.Types.Error                        (Validation)
import           Data.Morpheus.Types.Query.Operator               (Operator (..), RawOperator, ValidOperator,
                                                                   VariableDefinitions)
import           Data.Morpheus.Types.Query.RawSelection           (RawSelectionSet)
import           Data.Morpheus.Types.Query.Selection              (SelectionSet)
import           Data.Morpheus.Types.Types                        (GQLQueryRoot (..))
import           Data.Text                                        (Text)

updateQuery :: RawOperator -> SelectionSet -> ValidOperator
updateQuery (Query name' _ _ pos) sel    = Query name' [] sel pos
updateQuery (Mutation name' _ _ pos) sel = Mutation name' [] sel pos

fieldSchema :: [(Text, ObjectField)]
fieldSchema =
  [ ( "__schema"
    , ObjectField
        { args = []
        , fieldContent =
            SC.Field
              { SC.fieldName = "__schema"
              , SC.notNull = True
              , SC.asList = False
              , SC.kind = OBJECT
              , SC.fieldType = "__Schema"
              }
        })
  ]

setFieldSchema :: GObject ObjectField -> GObject ObjectField
setFieldSchema (GObject fields core) = GObject (fields ++ fieldSchema) core

getOperator :: RawOperator -> TypeLib -> Validation (OutputObject, VariableDefinitions, RawSelectionSet)
getOperator (Query _ args' sel _) lib' = pure (snd $ query lib', args', sel)
getOperator (Mutation _ args' sel position') lib' =
  case mutation lib' of
    Just (_, mutation') -> pure (mutation', args', sel)
    Nothing             -> Left $ mutationIsNotDefined position'

resolveValues :: TypeLib -> GQLQueryRoot -> Validation (OutputObject, SelectionSet)
resolveValues typesLib root = do
  (query', args', rawSel) <- getOperator (queryBody root) typesLib
  variables' <-
    resolveOperationVariables typesLib (fromList $ inputVariables root) (allVariableReferences [rawSel]) args'
  validateFragments typesLib root
  let operator' = setFieldSchema query'
  selection' <- resolveRawQuery typesLib (fragments root) variables' rawSel operator'
  pure (operator', selection')

preProcessQuery :: TypeLib -> GQLQueryRoot -> Validation ValidOperator
preProcessQuery lib' root' = do
  (operatorType', selection') <- resolveValues lib' root'
  selectors <- mapSelectorValidation lib' operatorType' selection'
  pure $ updateQuery (queryBody root') selectors