{-# LANGUAGE NamedFieldPuns         #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE OverloadedStrings      #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE MultiParamTypeClasses  #-}

module Data.Morpheus.Types.Internal.Validation.Error
  ( MissingRequired(..)
  , KindViolation(..)
  , Unknown(..)
  , InternalError(..)
  , Target(..)
  , Unused(..)
  )
  where

import           Data.Semigroup                 ((<>))

-- MORPHEUS
import           Data.Morpheus.Error.Utils      ( errorMessage )
import           Data.Morpheus.Error.Selection  ( unknownSelectionField )
import           Data.Morpheus.Types.Internal.Validation.Validator
                                                ( Context(..)
                                                , InputContext(..)
                                                , renderInputPrefix
                                                , Target(..)
                                                )
import           Data.Morpheus.Types.Internal.AST
                                                ( RESOLVED
                                                , Ref(..)
                                                , TypeRef(..)
                                                , GQLError(..)
                                                , GQLErrors
                                                , Argument(..)
                                                , ObjectEntry(..)
                                                , Fragment(..)
                                                , Fragments
                                                , Variable(..)
                                                , VariableDefinitions
                                                , FieldDefinition(..)
                                                , FieldsDefinition
                                                , InputFieldsDefinition
                                                , Schema
                                                , Object
                                                , Arguments
                                                , getOperationName
                                                )



class InternalError a where
  internalError :: a -> GQLError



instance InternalError FieldDefinition where
  internalError FieldDefinition
    { fieldName
    , fieldType = TypeRef { typeConName }
    } = GQLError
      { message
        = "INTERNAL: Type \"" <> typeConName
        <> "\" referenced by field \"" <> fieldName
        <> "\" can't found in Schema "
      , locations = []
      }



class Unused c where
  unused :: Context -> c -> GQLError

-- query M ( $v : String ) { a } -> "Variable \"$bla\" is never used in operation \"MyMutation\".",
instance Unused (Variable s) where
  unused
    Context { operationName }
    Variable{ variableName , variablePosition}
       = GQLError
        { message
            = "Variable \"$" <> variableName
            <> "\" is never used in operation \""
            <> getOperationName operationName <> "\"."
        , locations = [variablePosition]
        }

instance Unused Fragment where
  unused
    _
    Fragment { fragmentName , fragmentPosition }
      = GQLError
        { message
            = "Fragment \"" <> fragmentName
            <> "\" is never used."
        , locations = [fragmentPosition]
        }

class MissingRequired c ctx where
  missingRequired :: Context -> ctx -> Ref -> c -> GQLError

instance MissingRequired (Arguments s) ctx where
  missingRequired
    Context { scopePosition , scopeSelectionName }
    _
    Ref { refName  } _
    = GQLError
      { message
        = "Field \"" <> scopeSelectionName <> "\" argument \""
        <> refName <> "\" is required but not provided."
      , locations = [scopePosition]
      }

instance MissingRequired (Object s) InputContext where
  missingRequired
      Context { scopePosition }
      inputCTX
      Ref { refName  }
      _
    = GQLError
      { message
        =  renderInputPrefix inputCTX <> "Undefined Field \"" <> refName <> "\"."
      , locations = [scopePosition]
      }

instance MissingRequired (VariableDefinitions s) ctx where
  missingRequired
    Context { operationName }
    _
    Ref { refName , refPosition } _
    = GQLError
      { message
        = "Variable \"" <> refName
        <> "\" is not defined by operation \""
        <> getOperationName operationName <> "\"."
      , locations = [refPosition]
      }


class Unknown c ctx where
  type UnknownSelector c
  unknown :: Context -> ctx -> c -> UnknownSelector c -> GQLErrors

-- {...H} -> "Unknown fragment \"H\"."
instance Unknown Fragments ctx where
  type UnknownSelector Fragments = Ref
  unknown _ _ _ (Ref name pos)
    = errorMessage pos
      ("Unknown Fragment \"" <> name <> "\".")

instance Unknown Schema ctx where
  type UnknownSelector Schema = Ref
  unknown _ _ _ Ref { refName , refPosition }
    = errorMessage refPosition ("Unknown type \"" <> refName <> "\".")

instance Unknown FieldDefinition ctx where
  type UnknownSelector FieldDefinition = Argument RESOLVED
  unknown _ _ FieldDefinition { fieldName } Argument { argumentName, argumentPosition }
    = errorMessage argumentPosition
      ("Unknown Argument \"" <> argumentName <> "\" on Field \"" <> fieldName <> "\".")

instance Unknown InputFieldsDefinition InputContext where
  type UnknownSelector InputFieldsDefinition = ObjectEntry RESOLVED
  unknown Context { scopePosition } ctx _ ObjectEntry { entryName } =
    [
      GQLError
        { message = renderInputPrefix ctx <>"Unknown Field \"" <> entryName <> "\"."
        , locations = [scopePosition]
        }
    ]

instance Unknown FieldsDefinition ctx where
  type UnknownSelector FieldsDefinition = Ref
  unknown Context { scopeTypeName } _ _
    = unknownSelectionField scopeTypeName

class KindViolation (t :: Target) ctx where
  kindViolation :: c t -> ctx -> GQLError

instance KindViolation 'TARGET_OBJECT Fragment where
  kindViolation _ Fragment { fragmentName, fragmentType, fragmentPosition }
    = GQLError
    { message
      = "Fragment \"" <> fragmentName
        <> "\" cannot condition on non composite type \""
        <> fragmentType <>"\"."
    , locations = [fragmentPosition]
    }

instance KindViolation 'TARGET_INPUT (Variable s) where
  kindViolation _ Variable
      { variableName
      , variablePosition
      , variableType = TypeRef { typeConName }
      }
    = GQLError
      { message
        =  "Variable \"$" <> variableName
        <> "\" cannot be non-input type \""
        <> typeConName <>"\"."
      , locations = [variablePosition]
      }