{-# LANGUAGE QuasiQuotes #-}

-- | Schema parsers for common functionality of logical model resolvers.
module Hasura.LogicalModelResolver.Schema (argumentsSchema) where

import Data.HashMap.Strict qualified as HashMap
import Data.Monoid (Ap (Ap, getAp))
import Hasura.GraphQL.Schema.Backend
  ( BackendSchema (columnParser),
    MonadBuildSchema,
  )
import Hasura.GraphQL.Schema.Common
  ( SchemaT,
  )
import Hasura.GraphQL.Schema.Parser qualified as P
import Hasura.Prelude
import Hasura.RQL.IR.Value (openValueOrigin)
import Hasura.RQL.Types.Column qualified as Column
import Hasura.StoredProcedure.Metadata (ArgumentName (..))
import Hasura.StoredProcedure.Types (NullableScalarType (..))
import Language.GraphQL.Draft.Syntax qualified as G
import Language.GraphQL.Draft.Syntax.QQ qualified as G

-- | Schema parser for native query or stored procedure arguments.
argumentsSchema ::
  forall b r m n.
  (MonadBuildSchema b r m n) =>
  -- | The resolver description, such as "Stored Procedure" or "Native Query".
  Text ->
  -- | The resolver name.
  G.Name ->
  -- | Arguments
  HashMap ArgumentName (NullableScalarType b) ->
  MaybeT (SchemaT r m) (P.InputFieldsParser n (HashMap ArgumentName (Column.ColumnValue b)))
argumentsSchema resolverDesc resolverName argsSignature = do
  -- Lift 'SchemaT r m (InputFieldsParser ..)' into a monoid using Applicative.
  -- This lets us use 'foldMap' + monoid structure of hashmaps to avoid awkwardly
  -- traversing the arguments and building the resulting parser.
  argsParser <-
    getAp
      $ foldMap
        ( \(name, NullableScalarType {nstType, nstNullable, nstDescription}) -> Ap do
            argValueParser <-
              fmap (HashMap.singleton name . openValueOrigin)
                <$> lift (columnParser (Column.ColumnScalar nstType) (G.Nullability nstNullable))
            -- TODO: Naming conventions?
            -- TODO: Custom fields? (Probably not)
            argName <- hoistMaybe (G.mkName (getArgumentName name))
            let description = case nstDescription of
                  Just desc -> G.Description desc
                  Nothing -> G.Description (resolverDesc <> " argument " <> getArgumentName name)
            pure
              $ P.field
                argName
                (Just description)
                argValueParser
        )
        (HashMap.toList argsSignature)

  let desc = Just $ G.Description $ G.unName resolverName <> resolverDesc <> " Arguments"

  pure
    $ if null argsSignature
      then mempty
      else
        P.field
          [G.name|args|]
          desc
          (P.object (resolverName <> [G.name|_arguments|]) desc argsParser)