{-# LANGUAGE AllowAmbiguousTypes #-}

module Ema.Site (
  EmaSite (..),
  EmaStaticSite,
) where

import Control.Monad.Logger (MonadLoggerIO)
import Data.Some (Some)
import Ema.Asset (Asset)
import Ema.CLI qualified as CLI
import Ema.Dynamic (Dynamic)
import Ema.Route.Class (IsRoute (RouteModel))
import Optics.Core (Prism')
import UnliftIO (MonadUnliftIO)

{- | Typeclass to orchestrate an Ema site

  Given a route `r` from the class of `IsRoute` types, instantiating EmaSite
  on it enables defining the site build pipeline as follows:

  @
  SiteArg -> siteInput -> Dynamic model --[r, model]--> siteOutput
  @

  - `SiteArg` is typically not used, but it can be used to pass command line
  arguments and other such settings.
  - `siteInput` returns a time-varying value (Dynamic) representing the data for
  your static site.
  - `siteOutput` takes this data model (oneshot value) and returns the generated
  content (usually HTML asset, per `SiteOutput`) for the given route.

  Finally, `Ema.App.runSite @r arg` (where `arg` is of type `SiteArg`) is run
  from the `main` entry point to run your Ema site.
-}
class IsRoute r => EmaSite r where
  -- | `SiteArg` is typically settings from the environment (config file, or
  --    command-line arguments) that your Dynamic-producing `siteInput` function
  --    consumes as argument.
  type SiteArg r :: Type

  -- By default Ema sites have no site arguments.
  type SiteArg r = ()

  -- | Type of the value returned by `siteOutput`. Usually `Asset LByteString`
  -- but it can be anything.
  type SiteOutput r :: Type

  type SiteOutput r = Asset LByteString

  -- | Get the model's time-varying value as a `Dynamic`.
  --
  --    If your model is not time-varying, use `pure` to produce a constant value.
  siteInput ::
    forall m.
    (MonadIO m, MonadUnliftIO m, MonadLoggerIO m) =>
    Some CLI.Action ->
    -- | The value passed by the programmer to `Ema.App.runSite`
    SiteArg r ->
    -- | Time-varying value of the model. If your model is not time-varying, use
    -- `pure` to produce a constant value.
    m (Dynamic m (RouteModel r))

  -- | Return the output (typically an `Asset`) for the given route and model.
  siteOutput ::
    forall m.
    (MonadIO m, MonadLoggerIO m) =>
    Prism' FilePath r ->
    RouteModel r ->
    r ->
    m (SiteOutput r)

-- | Like `EmaSite` but `SiteOutput` is a bytestring `Asset`.
type EmaStaticSite r = (EmaSite r, SiteOutput r ~ Asset LByteString)