-- | This module provides functions that take API endpoint types and transform them into
-- HTTP Response outputs.
--
{-# LANGUAGE AllowAmbiguousTypes #-}
module Hreq.Core.Client.Internal where

import Control.Monad.Except
import Data.Proxy

import Hreq.Core.API
import Hreq.Core.Client.ClientError
import Hreq.Core.Client.HasRequest
import Hreq.Core.Client.HasResponse
import Hreq.Core.Client.Request
import Hreq.Core.Client.RunClient

-- | 'HasClient' represent constraints required to interpret a type level API into an actual
-- HTTP network request.
--
-- @ts ~@ 'ApiToReq' @api@ : Turns API description into a list of Request Content types
--
-- @v ~ 'GetVerb' api@     : Retrieves the verb component of an API definition
--
-- 'HasRequest' @ts v@     : Interprets type level list 'ReqContent' and 'Verb' components to obtain 'Request' Data
--
-- 'HasResponse' @v n@     : Interprets 'Verb' component to obtain type level specified Response
--
-- 'MonadError' 'ClientError' @n@ : @MonadError ClientError n@ is used by the 'httpRes' function
--
-- 'RunClient' @m@         : Provides capability to make an actual Http client network request
--
type HasClient api ts v n m =
  ( ts ~ ApiToReq api
  , v  ~ GetVerb api
  , HasRequest ts v
  , HasResponse v n
  , MonadError ClientError n
  , RunClient m
  )

-- | Used to make generic HTTP requests
--
hreq'
  :: forall api ts v m. HasClient api ts v (Either ClientError) m
  => Proxy api
  -> HttpInput ts
  -> m (HttpOutput v)
hreq' _ reqInput = do
  let req = httpReq (Proxy @v) (Proxy @ts) reqInput defaultRequest

  clientResponse <- runClient $! req

  lift' $ httpRes (Proxy @v) clientResponse
  where
    lift' = either throwHttpError pure

-- | Used to make HTTP requests. Uses visible type-applications for API type specification.
-- Example
--
-- > hreq @(Capture "age" Int :> GetJson Value) (25 :. Empty)
--
hreq
  :: forall api ts v m. HasClient api ts v (Either ClientError) m
  => HttpInput ts
  -> m (HttpOutput v)
hreq = hreq' (Proxy @api)

-- | 'HasStreamingClient' type constraint is represents constraints required for streaming HTTP responses.
--
-- @ts ~@ 'ApiToReq' @api@ :  Turns API description into a list of Request Content types
--
-- @v  ~@ 'GetVerb' @api@  :  Retrieves the verb component of an API definition
--
-- 'HasRequest' @ts v@     :  Interprets type level list 'ReqContent' and 'Verb' components to obtain
--
-- 'RunStreamingClient' @m a@: Provides capability to create an HTTP request with response streaming.
--
type HasStreamingClient api a ts v m =
  ( ts ~ ApiToReq api
  , v  ~ GetVerb api
  , HasRequest ts v
  , RunStreamingClient m a
  )

-- | Helper function for working with an HTTP response streaming client.
--
hreqStream
  :: forall api a ts v m r. (HasStreamingClient api a ts v m)
  => Proxy api
  -> HttpInput ts
  -> (a -> IO r)
  -> m r
hreqStream _ reqInput f = do
  let req = httpReq (Proxy @v) (Proxy @ts) reqInput defaultRequest

  withStreamingClient req (liftIO . f)