{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Convert Pandoc @Note@ inlines to inline sidenote HTML.
--
--   Each footnote becomes:
--   * A @<sup class="sidenote-ref">@ anchor in the body text.
--   * An @<aside class="sidenote">@ immediately following it, containing
--     the rendered note content.
--
--   On wide viewports, sidenotes.css floats asides into the right margin.
--   On narrow viewports they are hidden; the standard Pandoc-generated
--   @<section class="footnotes">@ at the document end serves as fallback.
module Filters.Sidenotes (apply) where

import           Control.Monad.State.Strict
import           Data.Default               (def)
import           Data.Text                  (Text)
import qualified Data.Text                  as T
import           Text.Pandoc.Class          (runPure)
import           Text.Pandoc.Definition
import           Text.Pandoc.Options        (WriterOptions)
import           Text.Pandoc.Walk           (walkM)
import           Text.Pandoc.Writers.HTML   (writeHtml5String)

-- | Transform all @Note@ inlines in the document to inline sidenote HTML.
apply :: Pandoc -> Pandoc
apply doc = evalState (walkM convertNote doc) (1 :: Int)

convertNote :: Inline -> State Int Inline
convertNote (Note blocks) = do
    n <- get
    put (n + 1)
    return $ RawInline "html" (renderNote n blocks)
convertNote x = return x

-- | Convert a 1-based counter to a letter label: 1→a, 2→b, … 26→z.
toLabel :: Int -> Text
toLabel n = T.singleton (toEnum (fromEnum 'a' + (n - 1) `mod` 26))

renderNote :: Int -> [Block] -> Text
renderNote n blocks =
    let inner = replacePTags (blocksToHtml blocks)
        lbl   = toLabel n
    in T.concat
        [ "<sup class=\"sidenote-ref\" id=\"snref-", lbl, "\">"
        ,   "<a href=\"#sn-", lbl, "\">", lbl, "</a>"
        , "</sup>"
        , "<span class=\"sidenote\" id=\"sn-", lbl, "\">"
        ,   "<sup class=\"sidenote-num\">", lbl, "</sup>\x00a0"
        ,   inner
        , "</span>"
        ]

-- | Replace <p> / </p> with inline-block spans so that sidenote content
--   stays valid inside the outer <span class="sidenote">.  A bare <p> inside
--   a <span> is invalid HTML and causes browsers to implicitly close the span.
replacePTags :: Text -> Text
replacePTags =
    T.replace "<p>" "<span class=\"sidenote-para\">"
    . T.replace "</p>" "</span>"

-- | Render a list of Pandoc blocks to an HTML fragment via a pure writer run.
blocksToHtml :: [Block] -> Text
blocksToHtml blocks =
    case runPure (writeHtml5String (def :: WriterOptions) (Pandoc mempty blocks)) of
        Left _  -> T.empty
        Right t -> t
