content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  
  module Data.Morpheus.Parsing.Internal.Terms
    ( name,
      variable,
      varName,
      ignoredTokens,
      parseString,
      collection,
      setOf,
      uniqTuple,
      uniqTupleOpt,
      parseTypeCondition,
      spreadLiteral,
      parseAlias,
      sepByAnd,
      parseName,
      parseType,
      keyword,
      optDescription,
      optionalCollection,
      parseTypeName,
      pipe,
      brackets,
      equal,
      colon,
      at,
      symbol,
    )
  where
  
  import Data.ByteString.Lazy.Internal (ByteString)
  import Data.Mergeable.IsMap (FromList)
  import Data.Morpheus.Ext.Result (GQLResult)
  import Data.Morpheus.Internal.Utils
    ( Empty (..),
      KeyOf,
      fromElems,
      fromLBS,
    )
  import Data.Morpheus.Parsing.Internal.Internal
    ( Parser,
      Position,
      getLocation,
    )
  import Data.Morpheus.Parsing.Internal.SourceText
    ( ignoredTokens,
      ignoredTokens1,
      parseStringBS,
    )
  import Data.Morpheus.Types.Internal.AST
    ( Description,
      FieldName,
      Ref (..),
      TypeName,
      TypeRef (..),
      TypeWrapper (..),
      packName,
    )
  import qualified Data.Morpheus.Types.Internal.AST as AST
  import Data.Morpheus.Types.Internal.AST.Name (Name)
  import Relude hiding (ByteString, empty, many)
  import Text.Megaparsec
    ( between,
      label,
      sepBy,
      sepBy1,
      sepEndBy,
      takeWhile1P,
      takeWhileP,
      try,
      (<?>),
    )
  import Text.Megaparsec.Byte
    ( char,
      string,
    )
  
  -- ':'
  #define COLON 58
  -- '@'
  #define AT 64
  -- '='
  #define EQUAL 61
  -- '|'
  #define PIPE 124
  -- '$'
  #define DOLLAR 36
  -- '&'
  #define AMPERSAND 38
  -- '_'
  #define UNDERSCORE 95
  -- '!'
  #define BANG 33
  
  #define CHAR_A 65
  
  #define CHAR_Z 90
  
  #define CHAR_a 97
  
  #define CHAR_z 122
  
  #define DIGIT_0 48
  
  #define DIGIT_9 57
  
  symbol :: Word8 -> Parser ()
  symbol x = char x *> ignoredTokens
  {-# INLINE symbol #-}
  
  colon :: Parser ()
  colon = symbol COLON
  {-# INLINE colon #-}
  
  at :: Parser ()
  at = symbol AT
  {-# INLINE at #-}
  
  equal :: Parser ()
  equal = symbol EQUAL
  {-# INLINE equal #-}
  
  pipe :: Parser a -> Parser [a]
  pipe x = optional (symbol PIPE) *> (x `sepBy1` symbol PIPE)
  {-# INLINE pipe #-}
  
  -- parens : '()'
  parens :: Parser a -> Parser a
  parens = between (symbol 40) (symbol 41)
  {-# INLINE parens #-}
  
  -- braces: {}
  braces :: Parser a -> Parser a
  braces = between (symbol 123) (symbol 125)
  {-# INLINE braces #-}
  
  -- brackets: []
  brackets :: Parser a -> Parser a
  brackets = between (symbol 91) (symbol 93)
  {-# INLINE brackets #-}
  
  -- 2.1.9 Names
  -- https://spec.graphql.org/draft/#Name
  -- Name
  name :: Parser AST.Token
  name =
    label "Name"
      $ fromLBS
      <$> do
        (<>) <$> takeWhile1P Nothing isStartChar <*> takeWhileP Nothing isContinueChar
      <* ignoredTokens
    where
      isStartChar x =
        (x >= CHAR_a && x <= CHAR_z)
          || (x >= CHAR_A && x <= CHAR_Z)
          || x
          == UNDERSCORE
      {-# INLINE isStartChar #-}
      isContinueChar x =
        isStartChar x
          || (x >= DIGIT_0 && x <= DIGIT_9) -- digit
      {-# INLINE isContinueChar #-}
  {-# INLINE name #-}
  
  parseName :: Parser (Name t)
  parseName = packName <$> name
  {-# INLINE parseName #-}
  
  parseTypeName :: Parser TypeName
  parseTypeName = packName <$> name
  {-# INLINE parseTypeName #-}
  
  keyword :: ByteString -> Parser ()
  keyword x = string x *> ignoredTokens1
  {-# INLINE keyword #-}
  
  varName :: Parser FieldName
  varName = symbol DOLLAR *> parseName <* ignoredTokens
  {-# INLINE varName #-}
  
  -- Variable : https://graphql.github.io/graphql-spec/June2018/#Variable
  --
  -- Variable :  $Name
  --
  variable :: Parser (Ref FieldName)
  variable =
    label "variable"
      $ flip Ref
      <$> getLocation
      <*> varName
  {-# INLINE variable #-}
  
  -- Descriptions: https://graphql.github.io/graphql-spec/June2018/#Description
  --
  -- Description:
  --   StringValue
  optDescription :: Parser (Maybe Description)
  optDescription = optional parseString
  {-# INLINE optDescription #-}
  
  parseString :: Parser AST.Token
  parseString = label "String" $ fromLBS <$> parseStringBS
  {-# INLINE parseString #-}
  
  ------------------------------------------------------------------------
  sepByAnd :: Parser a -> Parser [a]
  sepByAnd entry = entry `sepBy` (optional (symbol AMPERSAND) *> ignoredTokens)
  {-# INLINE sepByAnd #-}
  
  -----------------------------
  collection :: Parser a -> Parser [a]
  collection entry = braces (entry `sepEndBy` ignoredTokens)
  {-# INLINE collection #-}
  
  setOf :: (FromList GQLResult map k a, KeyOf k a) => Parser a -> Parser (map k a)
  setOf = collection >=> lift . fromElems
  {-# INLINE setOf #-}
  
  optionalCollection :: (Empty c) => Parser c -> Parser c
  optionalCollection x = x <|> pure empty
  {-# INLINE optionalCollection #-}
  
  parseNonNull :: Parser Bool
  parseNonNull = (symbol BANG $> True) <|> pure False
  {-# INLINE parseNonNull #-}
  
  uniqTuple :: (FromList GQLResult map k a, KeyOf k a) => Parser a -> Parser (map k a)
  uniqTuple parser =
    label "Tuple"
      $ parens
        (parser `sepBy` ignoredTokens <?> "empty Tuple value!")
      >>= lift
      . fromElems
  {-# INLINE uniqTuple #-}
  
  uniqTupleOpt ::
    ( FromList GQLResult map k a,
      Empty (map k a),
      KeyOf k a
    ) =>
    Parser a ->
    Parser (map k a)
  uniqTupleOpt x = uniqTuple x <|> pure empty
  {-# INLINE uniqTupleOpt #-}
  
  -- Type Conditions: https://graphql.github.io/graphql-spec/June2018/#sec-Type-Conditions
  --
  --  TypeCondition:
  --    on NamedType
  --
  parseTypeCondition :: Parser TypeName
  parseTypeCondition = keyword "on" *> parseTypeName
  {-# INLINE parseTypeCondition #-}
  
  spreadLiteral :: Parser Position
  spreadLiteral = getLocation <* string "..." <* ignoredTokens
  {-# INLINE spreadLiteral #-}
  
  -- Field Alias : https://graphql.github.io/graphql-spec/June2018/#sec-Field-Alias
  -- Alias
  --  Name:
  parseAlias :: Parser (Maybe FieldName)
  parseAlias = try (optional alias) <|> pure Nothing
    where
      alias = label "alias" (parseName <* colon)
  {-# INLINE parseAlias #-}
  
  parseType :: Parser TypeRef
  parseType = uncurry TypeRef <$> (unwrapped <|> wrapped)
    where
      unwrapped :: Parser (TypeName, TypeWrapper)
      unwrapped = (,) <$> parseTypeName <*> (BaseType <$> parseNonNull)
      {-# INLINE unwrapped #-}
      ----------------------------------------------
      wrapped :: Parser (TypeName, TypeWrapper)
      wrapped = do
        (typename, wrapper) <- brackets (unwrapped <|> wrapped)
        isRequired <- parseNonNull
        pure (typename, TypeList wrapper isRequired)
      {-# INLINE wrapped #-}
  {-# INLINE parseType #-}
  