content: {-# LANGUAGE UndecidableInstances #-}
  
  {-| Creation of routes from functions. A route is a handler function
  for single path of the server.
  -}
  module Mig.Core.Class.Route (
    Route (..),
    ToRoute (..),
    toRoute,
  ) where
  
  import Control.Monad.IO.Class
  import Data.OpenApi (ToParamSchema (..), ToSchema (..))
  import Data.Proxy
  import Data.String
  import Data.Text (Text)
  import GHC.TypeLits
  import Mig.Core.Class.MediaType
  import Mig.Core.Class.Monad
  import Mig.Core.Class.Response (IsResp (..))
  import Mig.Core.ServerFun
  import Mig.Core.Types
  import Web.FormUrlEncoded (FromForm)
  import Web.HttpApiData
  
  {-| Values that represent routes.
  A route is a function of arbitrary number of arguments. Where
  each argument is one of the special newtype-wrappers that
  read type-safe information from HTTP-request and return type of the route function
  is a value of something convertible to HTTP-request.
  -}
  class (MonadIO (MonadOf a)) => ToRoute a where
    -- | Update API info
    toRouteInfo :: RouteInfo -> RouteInfo
  
    -- | Convert to route
    toRouteFun :: a -> ServerFun (MonadOf a)
  
  -- | Route contains API-info and how to run it
  data Route m = Route
    { info :: RouteInfo
    -- ^ definition of the API (to use it in OpenApi or clients)
    , run :: ServerFun m
    -- ^ how to run a server
    }
  
  -- | converts route-like value to route.
  toRoute :: forall a. (ToRoute a) => a -> Route (MonadOf a)
  toRoute a =
    Route
      { info = toRouteInfo @a emptyRouteInfo
      , run = toRouteFun a
      }
  
  -------------------------------------------------------------------------------------
  -- identity instances
  
  instance (MonadIO m) => ToRoute (Route m) where
    toRouteInfo = id
    toRouteFun = (.run)
  
  -------------------------------------------------------------------------------------
  -- request inputs
  
  instance (ToSchema a, FromReqBody media a, ToRoute b) => ToRoute (Body media a -> b) where
    toRouteInfo = addBodyInfo @media @a . toRouteInfo @b
    toRouteFun f = withBody @media (toRouteFun . f . Body)
  
  instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Query sym a -> b) where
    toRouteInfo = addQueryInfo @sym @a . toRouteInfo @b
    toRouteFun f = withQuery (getName @sym) (toRouteFun . f . Query)
  
  instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Optional sym a -> b) where
    toRouteInfo = addOptionalInfo @sym @a . toRouteInfo @b
    toRouteFun f = withOptional (getName @sym) (toRouteFun . f . Optional)
  
  instance (ToRoute b, KnownSymbol sym) => ToRoute (QueryFlag sym -> b) where
    toRouteInfo = addQueryFlagInfo @sym . toRouteInfo @b
    toRouteFun f = withQueryFlag (getName @sym) (toRouteFun . f . QueryFlag)
  
  instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Capture sym a -> b) where
    toRouteInfo = addCaptureInfo @sym @a . toRouteInfo @b
    toRouteFun f = withCapture (getName @sym) (toRouteFun . f . Capture)
  
  instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (Header sym a -> b) where
    toRouteInfo = addHeaderInfo @sym @a . toRouteInfo @b
    toRouteFun f = withHeader (getName @sym) (toRouteFun . f . Header)
  
  instance (FromHttpApiData a, ToParamSchema a, ToRoute b, KnownSymbol sym) => ToRoute (OptionalHeader sym a -> b) where
    toRouteInfo = addOptionalHeaderInfo @sym @a . toRouteInfo @b
    toRouteFun f = withOptionalHeader (getName @sym) (toRouteFun . f . OptionalHeader)
  
  instance (FromForm a, ToRoute b) => ToRoute (Cookie a -> b) where
    toRouteInfo = addOptionalHeaderInfo @"Cookie" @Text . toRouteInfo @b
    toRouteFun f = withCookie (toRouteFun . f . Cookie)
  
  instance (ToRoute b) => ToRoute (PathInfo -> b) where
    toRouteInfo = toRouteInfo @b
    toRouteFun f = withPathInfo (toRouteFun . f . PathInfo)
  
  instance (ToRoute b) => ToRoute (FullPathInfo -> b) where
    toRouteInfo = toRouteInfo @b
    toRouteFun f = withFullPathInfo (toRouteFun . f . FullPathInfo)
  
  instance (ToRoute b) => ToRoute (RawRequest -> b) where
    toRouteInfo = toRouteInfo @b
    toRouteFun f = \req -> toRouteFun (f (RawRequest req)) req
  
  instance (ToRoute b) => ToRoute (IsSecure -> b) where
    toRouteInfo = toRouteInfo @b
    toRouteFun f = \req -> toRouteFun (f (IsSecure req.isSecure)) req
  
  -------------------------------------------------------------------------------------
  -- outputs
  
  instance {-# OVERLAPPABLE #-} (MonadIO m, IsResp a, IsMethod method) => ToRoute (Send method m a) where
    toRouteInfo = setMethod (toMethod @method) (getMedia @a)
    toRouteFun (Send a) = sendResponse $ toResponse <$> a
  
  ---------------------------------------------
  -- utils
  
  getName :: forall sym a. (KnownSymbol sym, IsString a) => a
  getName = fromString (symbolVal (Proxy @sym))
  