{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Inline SVG score fragments into the Pandoc AST.
--
-- Fenced-div syntax in Markdown:
--
-- > :::score-fragment{score-name="Main Theme, mm. 1–8" score-caption="The opening gesture."}
-- > ![](scores/main-theme.svg)
-- > :::
--
-- The filter reads the referenced SVG from disk (path resolved relative to
-- the source file's directory), replaces hardcoded black fills/strokes with
-- @currentColor@ for dark-mode compatibility, and emits a @\<figure\>@ with
-- the appropriate exhibit attributes for gallery.js TOC integration.
module Filters.Score (inlineScores) where

import           Data.Maybe             (listToMaybe)
import qualified Data.Text              as T
import qualified Data.Text.IO           as TIO
import           System.FilePath        ((</>))
import           Text.Pandoc.Definition
import           Text.Pandoc.Walk       (walkM)

-- | Walk the Pandoc AST and inline all score-fragment divs.
--   @baseDir@ is the directory of the source file; image paths in the
--   fenced-div are resolved relative to it.
inlineScores :: FilePath -> Pandoc -> IO Pandoc
inlineScores baseDir = walkM (inlineScore baseDir)

inlineScore :: FilePath -> Block -> IO Block
inlineScore baseDir (Div (_, cls, attrs) blocks)
    | "score-fragment" `elem` cls = do
        let mName    = lookup "score-name"    attrs
            mCaption = lookup "score-caption" attrs
            mPath    = findImagePath blocks
        case mPath of
            Nothing   -> return $ Div ("", cls, attrs) blocks
            Just path -> do
                let fullPath = baseDir </> T.unpack path
                svgRaw <- TIO.readFile fullPath
                let html = buildHtml mName mCaption (processColors svgRaw)
                return $ RawBlock (Format "html") html
inlineScore _ block = return block

-- | Extract the image src from the first Para that contains an Image inline.
findImagePath :: [Block] -> Maybe T.Text
findImagePath blocks = listToMaybe
    [ src
    | Para inlines       <- blocks
    , Image _ _ (src, _) <- inlines
    ]

-- | Replace hardcoded black fill/stroke values with @currentColor@ so the
--   SVG inherits the CSS @color@ property in both light and dark modes.
--
--   6-digit hex patterns are at the bottom of the composition chain
--   (applied first) so they are replaced before the 3-digit shorthand,
--   preventing partial matches (e.g. @#000@ matching the prefix of @#000000@).
processColors :: T.Text -> T.Text
processColors
    -- 3-digit hex and keyword patterns (applied after 6-digit replacements)
    = T.replace "fill=\"#000\""       "fill=\"currentColor\""
    . T.replace "fill=\"black\""      "fill=\"currentColor\""
    . T.replace "stroke=\"#000\""    "stroke=\"currentColor\""
    . T.replace "stroke=\"black\""   "stroke=\"currentColor\""
    . T.replace "fill:#000"          "fill:currentColor"
    . T.replace "fill:black"         "fill:currentColor"
    . T.replace "stroke:#000"       "stroke:currentColor"
    . T.replace "stroke:black"      "stroke:currentColor"
    -- 6-digit hex patterns (applied first — bottom of the chain)
    . T.replace "fill=\"#000000\""    "fill=\"currentColor\""
    . T.replace "stroke=\"#000000\"" "stroke=\"currentColor\""
    . T.replace "fill:#000000"       "fill:currentColor"
    . T.replace "stroke:#000000"    "stroke:currentColor"

buildHtml :: Maybe T.Text -> Maybe T.Text -> T.Text -> T.Text
buildHtml mName mCaption svgContent = T.concat
    [ "<figure class=\"score-fragment exhibit\""
    , maybe "" (\n -> " data-exhibit-name=\"" <> escHtml n <> "\"") mName
    , " data-exhibit-type=\"score\">"
    , "<div class=\"score-fragment-inner\">"
    , svgContent
    , "</div>"
    , maybe "" (\c -> "<figcaption class=\"score-caption\">" <> escHtml c <> "</figcaption>") mCaption
    , "</figure>"
    ]

escHtml :: T.Text -> T.Text
escHtml = T.replace "\"" "&quot;"
        . T.replace ">"  "&gt;"
        . T.replace "<"  "&lt;"
        . T.replace "&"  "&amp;"
