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

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

import Data.Morpheus.Error.Selection (unknownSelectionField)
import Data.Morpheus.Types.Internal.AST
  ( Argument (..),
    Arguments,
    Directive (..),
    FieldName,
    Fragment (..),
    FragmentName,
    GQLError,
    IMPLEMENTABLE,
    IN,
    Object,
    ObjectEntry (..),
    Ref (..),
    TypeCategory,
    TypeName,
    TypeRef (..),
    Variable (..),
    VariableDefinitions,
    at,
    atPositions,
    getOperationName,
    msg,
    withPath,
  )
import Data.Morpheus.Types.Internal.Validation.Validator
  ( InputContext (..),
    OperationContext (..),
    Scope (..),
    ScopeKind (..),
    renderInputPrefix,
  )
import Relude

class Unused c where
  unused :: OperationContext s1 s2 -> c -> GQLError

-- query M ( $v : String ) { a } -> "Variable \"$bla\" is never used in operation \"MyMutation\".",
instance Unused (Variable s) where
  unused
    OperationContext {operationName}
    Variable {variableName, variablePosition} =
      ( "Variable "
          <> msg ("$" <> variableName)
          <> " is never used in operation "
          <> msg (getOperationName operationName)
          <> "."
      )
        `at` variablePosition

instance Unused (Fragment s) where
  unused
    _
    Fragment {fragmentName, fragmentPosition} =
      ( "Fragment "
          <> msg fragmentName
          <> " is never used."
      )
        `at` fragmentPosition

class MissingRequired c ctx where
  missingRequired :: Scope -> ctx -> Ref FieldName -> c -> GQLError

instance MissingRequired (Arguments s) ctx where
  missingRequired
    Scope {position, kind, fieldName, path}
    _
    Ref {refName}
    _ =
      ( ( inScope kind
            <> " argument "
            <> msg refName
            <> " is required but not provided."
        )
          `atPositions` position
      )
        `withPath` path
      where
        inScope DIRECTIVE = "Directive " <> msg fieldName
        inScope _ = "Field " <> msg fieldName

instance MissingRequired (Object s) (InputContext ctx) where
  missingRequired
    Scope {position, path}
    ctx
    Ref {refName}
    _ =
      ( ( renderInputPrefix
            ctx
            <> "Undefined Field "
            <> msg refName
            <> "."
        )
          `atPositions` position
      )
        `withPath` path

instance MissingRequired (VariableDefinitions s) (OperationContext s1 s2) where
  missingRequired
    Scope {path}
    OperationContext
      { operationName
      }
    Ref {refName, refPosition}
    _ =
      ( ( "Variable "
            <> msg refName
            <> " is not defined by operation "
            <> msg (getOperationName operationName)
            <> "."
        )
          `at` refPosition
      )
        `withPath` path

class Unknown ref ctx where
  unknown :: Scope -> ctx -> ref -> GQLError

-- {...H} -> "Unknown fragment \"H\"."
instance Unknown (Ref FragmentName) ctx where
  unknown Scope {path} _ Ref {refName, refPosition} =
    (("Unknown Fragment " <> msg refName <> ".") `at` refPosition) `withPath` path

instance Unknown (Ref TypeName) ctx where
  unknown Scope {path} _ Ref {refName, refPosition} =
    (("Unknown type " <> msg refName <> ".") `at` refPosition) `withPath` path

instance Unknown (Argument s') ctx where
  unknown Scope {kind, path, fieldName} _ Argument {argumentName, argumentPosition} =
    ( ( "Unknown Argument "
          <> msg argumentName
          <> " on "
          <> scope kind
          <> " "
          <> msg fieldName
          <> "."
      )
        `at` argumentPosition
    )
      `withPath` path
    where
      scope DIRECTIVE = "Directive"
      scope _ = "Field"

instance Unknown (Ref FieldName) ctx where
  unknown Scope {currentTypeName, path} _ ref =
    unknownSelectionField currentTypeName ref `withPath` path

instance Unknown (ObjectEntry valueS) (InputContext ctx) where
  unknown
    Scope {position, path}
    ctx
    ObjectEntry {entryName} =
      ( ( renderInputPrefix ctx
            <> "Unknown Field "
            <> msg entryName
            <> "."
        )
          `atPositions` position
      )
        `withPath` path

instance Unknown (Directive s') ctx where
  unknown Scope {path} _ Directive {directiveName, directivePosition} =
    (("Unknown Directive " <> msg directiveName <> ".") `at` directivePosition) `withPath` path

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

instance KindViolation IMPLEMENTABLE (Fragment s) where
  kindViolation _ Fragment {fragmentName, fragmentType, fragmentPosition} =
    ( "Fragment "
        <> msg fragmentName
        <> " cannot condition on non composite type "
        <> msg fragmentType
        <> "."
    )
      `at` fragmentPosition

instance KindViolation IN (Variable s) where
  kindViolation
    _
    Variable
      { variableName,
        variablePosition,
        variableType = TypeRef {typeConName}
      } =
      ( "Variable "
          <> msg ("$" <> variableName)
          <> " cannot be non-input type "
          <> msg typeConName
          <> "."
      )
        `at` variablePosition
