{-# LANGUAGE GADTs            #-}
{-# LANGUAGE NamedFieldPuns   #-}
{-# LANGUAGE RecordWildCards  #-}

module Data.Morpheus.Validation.Query.Arguments
  ( validateArguments
  )
where

import           Data.Foldable                  (traverse_)
import           Data.Morpheus.Types.Internal.AST
                                                ( Argument(..)
                                                , ArgumentsDefinition(..)
                                                , Arguments
                                                , ArgumentDefinition
                                                , FieldDefinition(..)
                                                , TypeRef(..)
                                                , Value(..)
                                                , RawValue
                                                , ResolvedValue
                                                , RESOLVED
                                                , VALID
                                                , ObjectEntry(..)
                                                , RAW
                                                )
import           Data.Morpheus.Types.Internal.Operation
                                                ( Listable(..)
                                                , empty
                                                )
import           Data.Morpheus.Types.Internal.Validation
                                                ( SelectionValidator
                                                , InputSource(..)
                                                , SelectionContext(..)
                                                , selectKnown
                                                , selectRequired
                                                , selectWithDefaultValue
                                                , askScopePosition
                                                , withScopePosition
                                                , askInputFieldType
                                                , startInput
                                                , askContext
                                                )
import           Data.Morpheus.Validation.Internal.Value
                                                ( validateInput )

-- only Resolves , doesnot checks the types
resolveObject :: RawValue -> SelectionValidator ResolvedValue
resolveObject = resolve
 where
  resolveEntry :: ObjectEntry RAW -> SelectionValidator (ObjectEntry RESOLVED)
  resolveEntry (ObjectEntry name v) = ObjectEntry name <$> resolve v
  ------------------------------------------------
  resolve :: RawValue -> SelectionValidator ResolvedValue
  resolve Null         = pure Null
  resolve (Scalar x  ) = pure $ Scalar x
  resolve (Enum   x  ) = pure $ Enum x
  resolve (List   x  ) = List <$> traverse resolve x
  resolve (Object obj) = Object <$> traverse resolveEntry obj
  resolve (VariableValue ref) =
     variables <$> askContext
    >>= fmap (ResolvedVariable ref)
        . selectRequired ref

resolveArgumentVariables
  :: Arguments RAW
  -> SelectionValidator (Arguments RESOLVED)
resolveArgumentVariables
  = traverse resolveVariable
 where
  resolveVariable :: Argument RAW -> SelectionValidator (Argument RESOLVED)
  resolveVariable (Argument key val position) = do
    constValue <- resolveObject val
    pure $ Argument key constValue position

validateArgument
  :: Arguments RESOLVED
  -> ArgumentDefinition
  -> SelectionValidator (Argument VALID)
validateArgument
    requestArgs
    argumentDef@FieldDefinition
      { fieldName
      , fieldType = TypeRef { typeWrappers }
      }
  = do
      argumentPosition <- askScopePosition
      argument <- selectWithDefaultValue
          Argument { argumentName = fieldName, argumentValue = Null, argumentPosition }
          argumentDef
          requestArgs
      validateArgumentValue argument
 where
  -------------------------------------------------------------------------
  validateArgumentValue :: Argument RESOLVED -> SelectionValidator (Argument VALID)
  validateArgumentValue arg@Argument { argumentValue = value, .. } =
    withScopePosition argumentPosition
    $ startInput (SourceArgument arg)
    $ do
      datatype <- askInputFieldType argumentDef
      argumentValue <- validateInput
                typeWrappers
                datatype
                (ObjectEntry fieldName value)
      pure Argument { argumentValue , .. }

validateArguments
  :: FieldDefinition
  -> Arguments RAW
  -> SelectionValidator (Arguments VALID)
validateArguments
    fieldDef@FieldDefinition {  fieldArgs }
    rawArgs
  = do
    args <- resolveArgumentVariables rawArgs
    traverse_ checkUnknown (toList args)
    traverse (validateArgument args) argsDef
 where
  argsDef = case fieldArgs of
    (ArgumentsDefinition _ argsD) -> argsD
    NoArguments -> empty
  -------------------------------------------------
  checkUnknown :: Argument RESOLVED -> SelectionValidator ArgumentDefinition
  checkUnknown = (`selectKnown` fieldDef)