{-# LANGUAGE FlexibleContexts              #-}
{-# LANGUAGE DataKinds                     #-}
{-# LANGUAGE PolyKinds                     #-}
{-# LANGUAGE GADTs                         #-}
{-# LANGUAGE TypeOperators                 #-}
{-# LANGUAGE ScopedTypeVariables           #-}
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
{-|
Module      : Knit.Effect.Html
Description : Polysemy writer effects for creating Lucid/Blaze documents
Copyright   : (c) Adam Conner-Sax 2019
License     : BSD-3-Clause
Maintainer  : adam_conner_sax@yahoo.com
Stability   : experimental

Create a Lucid or Blaze html document (using a Writer to intersperse html and other code) and then use the 'Knit.Haskell.Docs' <https://github.com/isovector/polysemy#readme polysemy> effect
to store that document for processing/output later.
-}
module Knit.Effect.Html
  (
    -- * Lucid

    -- ** Effects
    Lucid
  , LucidDocs

    -- ** Actions
  , lucid

    -- ** Intepretations
  , lucidToNamedText
  , lucidHtml
  , lucidToText
  , newLucidDoc

  -- * Blaze

  -- ** Effects
  , Blaze
  , BlazeDocs

  -- ** Actions
  , blaze

  -- ** Interpretations
  , blazeToNamedText
  , blazeHtml
  , blazeToText
  , newBlazeDoc

    -- * Re-exports
  , NamedDoc(..)
  )
where

import qualified Polysemy                      as P
import qualified Polysemy.Writer               as P

import qualified Lucid                         as LH
import qualified Text.Blaze.Html               as BH
import qualified Text.Blaze.Html.Renderer.Text as BH
import qualified Data.Text.Lazy                as TL
import qualified Data.Text                     as T

import           Knit.Effect.Docs               ( Docs
                                                , NamedDoc(..)
                                                , newDoc
                                                , toNamedDocList
                                                )

-- For now, just handle the Html () case since then it's monoidal and we can interpret via writer
--newtype FreerHtml = FreerHtml { unFreer :: H.Html () }

-- | Type-Alias for a single Lucid document writer.
type Lucid = P.Writer (LH.Html ())

-- | Type-Alias for a single Blaze document writer.
type Blaze = P.Writer BH.Html

-- | Add a Lucid html fragment to the current Lucid doc.
lucid :: P.Member Lucid effs => LH.Html () -> P.Sem effs ()
lucid = P.tell

-- | Add a Blaze html fragment to the current Blaze doc.
blaze :: P.Member Blaze effs => BH.Html -> P.Sem effs ()
blaze = P.tell

-- | Type-Alias for the 'Knit.Effects.Docs' effect (multi-document Writer), specialized to Lucid docs.
-- To be used in an app that produces multiple html outputs, built up from Lucid bits.
type LucidDocs = Docs (LH.Html ())

-- | Type-Alias for the 'Knit.Effects.Docs' effect (multi-document Writer) specialized to Blaze docs.
-- To be used in an app that produces multiple html outputs, built up from Blaze bits.
type BlazeDocs = Docs BH.Html

-- | Take the current Lucid HTML in the writer and add it to the set of named docs with the given name.
-- NB: Only use this function for making sets of documents built exclusively from Lucid.  Otherwise use the more general Pandoc infrastructure in
-- 'Knit.Effects.Pandoc'.
newLucidDoc
  :: P.Member LucidDocs effs
  => T.Text
  -> P.Sem (Lucid ': effs) ()
  -> P.Sem effs ()
newLucidDoc n l = (fmap fst $ P.runWriter l) >>= newDoc n

-- | take the current Blaze HTML in the writer and add it to the set of named docs with the given name
-- NB: Only use this function for making sets of documents built exclusively from Blaze. Otherwise use the more general Pandoc infrastructure in
-- 'Knit.Effects.Pandoc'.
newBlazeDoc
  :: P.Member BlazeDocs effs
  => T.Text
  -> P.Sem (Blaze ': effs) ()
  -> P.Sem effs ()
newBlazeDoc n l = (fmap fst $ P.runWriter l) >>= newDoc n

-- | Interpret the LucidDocs effect (via Writer), producing a list of named Lucid docs, suitable for writing to disk.
lucidToNamedText
  :: P.Sem (LucidDocs ': effs) () -> P.Sem effs [NamedDoc TL.Text]
lucidToNamedText = fmap (fmap (fmap LH.renderText)) . toNamedDocList -- monad, list, NamedDoc itself

-- | Interpret the BlazeDocs effect (via Writer), producing a list of named Blaze docs.
blazeToNamedText
  :: P.Sem (BlazeDocs ': effs) () -> P.Sem effs [NamedDoc TL.Text]
blazeToNamedText = fmap (fmap (fmap BH.renderHtml)) . toNamedDocList -- monad, list, NamedDoc itself

-- | Interprest the Lucid effect (via Writer), producing a Lucid @Html ()@ from the currently written doc
lucidHtml :: P.Sem (Lucid ': effs) () -> P.Sem effs (LH.Html ())
lucidHtml = fmap fst . P.runWriter

-- | Interpret the Lucid effect (via Writer), producing @Text@ from the currently written doc
lucidToText :: P.Sem (Lucid ': effs) () -> P.Sem effs TL.Text
lucidToText = fmap LH.renderText . lucidHtml

-- | Interpret the Blaze effect (via Writer), producing a Blaze @Html@ from the currently written doc.
blazeHtml :: P.Sem (Blaze ': effs) () -> P.Sem effs BH.Html
blazeHtml = fmap fst . P.runWriter

-- | Interpret the Blaze effect (via Writer), producing @Text@ from the currently written doc.
blazeToText :: P.Sem (Blaze ': effs) () -> P.Sem effs TL.Text
blazeToText = fmap BH.renderHtml . blazeHtml