{-# LANGUAGE NamedFieldPuns #-}
module Data.Morpheus.Validation.Document.Validation
  ( validatePartialDocument
  ) where
import           Data.Maybe
import           Data.Morpheus.Error.Document.Interface  (ImplementsError (..), partialImplements, unknownInterface)
import           Data.Morpheus.Rendering.RenderGQL       (RenderGQL (..))
import           Data.Morpheus.Types.Internal.Data       (DataField (..), DataFullType (..), DataObject, DataTyCon (..),
                                                          Key, RawDataType (..), TypeAlias (..), isWeaker, isWeaker)
import           Data.Morpheus.Types.Internal.Validation (Validation)
validatePartialDocument :: [(Key, RawDataType)] -> Validation [(Key, DataFullType)]
validatePartialDocument lib = catMaybes <$> traverse validateType lib
  where
    validateType :: (Key, RawDataType) -> Validation (Maybe (Key, DataFullType))
    validateType (name, FinalDataType x)              = pure $ Just (name, x)
    validateType (name, Implements interfaces object) = asTuple name <$> object `mustImplement` interfaces
    validateType _                                    = pure Nothing
    
    asTuple name x = Just (name, x)
    
    mustImplement :: DataObject -> [Key] -> Validation DataFullType
    mustImplement object interfaceKey = do
      interface <- traverse getInterfaceByKey interfaceKey
      case concatMap (mustBeSubset object) interface of
        []     -> pure $ OutputObject object
        errors -> Left $ partialImplements (typeName object) errors
    
    mustBeSubset :: DataObject -> DataObject -> [(Key, Key, ImplementsError)]
    mustBeSubset DataTyCon {typeData = objFields} DataTyCon {typeName, typeData = interfaceFields} =
      concatMap checkField interfaceFields
      where
        checkField :: (Key, DataField) -> [(Key, Key, ImplementsError)]
        checkField (key, DataField {fieldType = interfaceT@TypeAlias { aliasTyCon = interfaceTypeName
                                                                     , aliasWrappers = interfaceWrappers
                                                                     }}) =
          case lookup key objFields of
            Just DataField {fieldType = objT@TypeAlias {aliasTyCon, aliasWrappers}}
              | aliasTyCon == interfaceTypeName && not (isWeaker aliasWrappers interfaceWrappers) -> []
              | otherwise ->
                [(typeName, key, UnexpectedType {expectedType = render interfaceT, foundType = render objT})]
            Nothing -> [(typeName, key, UndefinedField)]
    
    getInterfaceByKey :: Key -> Validation DataObject
    getInterfaceByKey key =
      case lookup key lib of
        Just (Interface x) -> pure x
        _                  -> Left $ unknownInterface key