{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

-- Copyright 2023 Gabriel Doriath Döhler, Délégation Générale Numérique (DGNum)

import Control.Applicative ((<|>))
import Control.Monad (void, (<=<))
import Data.List (intersperse)
import Data.Map qualified as M
import Data.Monoid (First (..))
import Hakyll
import Text.Blaze.Html.Renderer.String (renderHtml)
import Text.Blaze.Html5 qualified as H
import Text.Blaze.Html5.Attributes qualified as A
import Text.Pandoc.Definition
  ( Block (Header, Para, Plain),
    Inline (..),
    Pandoc (..),
  )
import Text.Pandoc.Options (HTMLMathMethod (MathML), WriterOptions (..))
import Text.Pandoc.Walk (query, walk)

--------------------------------------------------------------------------------

nbPostsHome :: Int
nbPostsHome = 10

poleName :: M.Map String String
poleName =
  M.fromList
    [ ("administration-systeme", "Administration Système"),
      ("communication", "Communication"),
      ("formation", "Formation"),
      ("geopolitique", "Géopolitique"),
      ("juridique", "Juridique"),
      ("logiciel", "Logiciel"),
      ("recherche", "Recherche")
    ]

patternFrom :: [String] -> [String] -> Pattern
patternFrom basenames extensions =
  foldl1 (.||.) $ map aux basenames
  where
    aux basename =
      foldl1 (.||.) $ map (fromGlob . (++) "./**.") extensions

postPattern :: Pattern
postPattern = fromGlob "posts/**.md"

filePattern :: Pattern
filePattern =
  patternFrom
    ["."]
    [ "djvu",
      "gif",
      "ico",
      "jpeg",
      "jpg",
      "pdf",
      "png",
      "ps",
      "svg",
      "tex",
      "txt"
    ]

--------------------------------------------------------------------------------

main :: IO ()
main = hakyll $ do
  tags <- extractMeta "tags"
  authors <- extractMeta "authors"
  poles <- extractMeta "poles"

  -- Static files
  match ("vendor/**" .||. "images/**" .||. "documents/**" .||. filePattern) $ do
    route idRoute
    compile copyFileCompiler

  -- Minified CSS and JS
  match ("js/*.min.js" .||. "css/*.min.css") $ do
    route idRoute
    compile copyFileCompiler

  -- Regular CSS
  match "css/*.css" $ do
    route idRoute
    compile compressCssCompiler

  -- Index page
  match "index.html" $ do
    route idRoute
    compile $ do
      allPosts <- recentFirst =<< loadAll ("posts/**.md" .&&. hasNoVersion)
      let posts = take nbPostsHome allPosts
      let ctx =
            listField "posts" postCtx (return posts)
              <> context

      getResourceBody
        >>= applyAsTemplate ctx
        >>= loadAndApplyTemplate "templates/default.html" ctx
        >>= relativizeUrls

  -- Regular pages
  match "about.html" $ do
    route idRoute
    compile $ do
      getResourceBody
        >>= applyAsTemplate context
        >>= loadAndApplyTemplate "templates/default.html" context
        >>= relativizeUrls
  
  match "legals.html" $ do
    route idRoute
    compile $ do
      getResourceBody
        >>= applyAsTemplate context
        >>= loadAndApplyTemplate "templates/default.html" context
        >>= relativizeUrls

  match ("faq.md" .||. "services.md" .||. "contact.md") $ do
    route $ setExtension "html"
    compile $
      customPandocCompiler
        >>= loadAndApplyTemplate "templates/default.html" (postCtxWith tags authors poles)
        >>= relativizeUrls

  -- Archives (i.e. list of all posts)
  match "archives.html" $ do
    route idRoute
    compile $ do
      allPosts <- recentFirst =<< loadAll ("posts/**.md" .&&. hasNoVersion)
      let ctx =
            listField "posts" postCtx (return allPosts)
              <> context

      getResourceBody
        >>= applyAsTemplate ctx
        >>= loadAndApplyTemplate "templates/default.html" ctx
        >>= relativizeUrls

  -- Invidual posts
  match "posts/**.md" $ do
    route $ setExtension "html"
    compile $
      customPandocCompiler
        >>= loadAndApplyTemplate "templates/post.html" (postCtxWith tags authors poles)
        >>= loadAndApplyTemplate "templates/default.html" (postCtxWith tags authors poles)
        >>= relativizeUrls

  -- Divisions
  tagsRules poles $ \_ polesPattern -> do
    route idRoute
    compile $ do
      posts <- loadAll polesPattern >>= recentFirst
      let ctx =
            listField "posts" postCtx (return posts)
              <> context

      getResourceBody
        >>= applyAsTemplate ctx
        >>= loadAndApplyTemplate "templates/division.html" ctx
        >>= loadAndApplyTemplate "templates/default.html" ctx
        >>= relativizeUrls

  match "poles/*.html" $ do
    route idRoute
    compile $ do
      getResourceBody
        >>= applyAsTemplate context
        >>= loadAndApplyTemplate "templates/division.html" context
        >>= loadAndApplyTemplate "templates/default.html" context
        >>= relativizeUrls

  create ["poles.html"] $ do
    route idRoute
    compile (polePanelCompiler poles)

  -- Authors
  tagsRules authors $ \authorStr authorsPattern -> do
    let title = "Articles écrits par « " ++ authorStr ++ " »"
    route idRoute
    compile $ do
      posts <- loadAll authorsPattern >>= recentFirst
      let ctx =
            constField "title" title
              <> listField "posts" postCtx (return posts)
              <> context

      makeItem ""
        >>= loadAndApplyTemplate "templates/author.html" ctx
        >>= loadAndApplyTemplate "templates/default.html" ctx
        >>= relativizeUrls

  create ["authors.html"] $ do
    route idRoute
    compile (authorPanelCompiler authors)

  -- Tags
  tagsRules tags $ \tagStr tagsPattern -> do
    let title = "Articles relatifs à « " ++ tagStr ++ " »"
    route idRoute
    compile $ do
      posts <- loadAll tagsPattern >>= recentFirst
      let ctx =
            constField "title" title
              <> listField "posts" postCtx (return posts)
              <> context

      makeItem ""
        >>= loadAndApplyTemplate "templates/tag.html" ctx
        >>= loadAndApplyTemplate "templates/default.html" ctx
        >>= relativizeUrls

  create ["tags.html"] $ do
    route idRoute
    compile (tagPanelCompiler tags)

  -- Partial templates to be used elsewhere
  match "templates/*" $ compile templateBodyCompiler

  -- Markdown version of posts
  match "posts/**.md" $ version "raw" $ do
    route idRoute
    compile $ getResourceBody >>= relativizeUrls

--------------------------------------------------------------------------------

-- Metadata extraction
extractMeta :: String -> Rules Tags
extractMeta name = do
  tags <- buildTagsWith (getTagsByField name) postPattern (fromCapture (fromGlob (name ++ "/*.html")))
  return (sortTagsBy caseInsensitiveTags tags)

-- Renderers
makeAuthorPanelLink :: String -> String -> Int -> Int -> Int -> String
makeAuthorPanelLink author url count _ _ =
  renderHtml $ H.a H.! A.href (H.toValue url) H.! A.class_ "panel-block" $ do
    H.span H.! A.class_ "tag mr-3" $ H.toHtml (pluralize count "publication")
    H.toHtml author

authorPanelCompiler :: Tags -> Compiler (Item String)
authorPanelCompiler authors = do
  let ctx =
        field "authors" (\_ -> renderTags makeAuthorPanelLink (mconcat . intersperse "\n") authors)
          <> constField "title" "Liste des auteur·e·s"
          <> context

  makeItem ""
    >>= loadAndApplyTemplate "templates/authors.html" ctx
    >>= loadAndApplyTemplate "templates/default.html" ctx
    >>= relativizeUrls

makePolePanelLink :: String -> String -> Int -> Int -> Int -> String
makePolePanelLink pole url count _ _ =
  renderHtml $ H.a H.! A.href (H.toValue url) H.! A.class_ "panel-block" $ do
    H.span H.! A.class_ "tag mr-3" $ H.toHtml (pluralize count "publication")
    H.toHtml (poleName M.! pole)

polePanelCompiler :: Tags -> Compiler (Item String)
polePanelCompiler poles = do
  let ctx =
        field "poles" (\_ -> renderTags makePolePanelLink (mconcat . intersperse "\n") poles)
          <> constField "title" "Liste des pôles"
          <> context

  makeItem ""
    >>= loadAndApplyTemplate "templates/poles.html" ctx
    >>= loadAndApplyTemplate "templates/default.html" ctx
    >>= relativizeUrls

makeTagPanelLink :: String -> String -> Int -> Int -> Int -> String
makeTagPanelLink tag url count _ _ =
  renderHtml $ H.a H.! A.href (H.toValue url) H.! A.class_ "panel-block" $ do
    H.span H.! A.class_ "tag mr-3" $ H.toHtml (pluralize count "publication")
    H.toHtml tag

tagPanelCompiler :: Tags -> Compiler (Item String)
tagPanelCompiler tags = do
  let ctx =
        field "tags" (\_ -> renderTags makeTagPanelLink (mconcat . intersperse "\n") tags)
          <> constField "title" "Liste des catégories"
          <> context

  makeItem ""
    >>= loadAndApplyTemplate "templates/tags.html" ctx
    >>= loadAndApplyTemplate "templates/default.html" ctx
    >>= relativizeUrls

renderTagLink :: String -> Maybe FilePath -> Maybe H.Html
renderTagLink _ Nothing = Nothing
renderTagLink tag (Just filePath) =
  Just
    $ H.a
      H.! A.title (H.stringValue $ "Pages relatives à « " ++ tag ++ " »")
      H.! A.class_ "tag is-link"
      H.! A.href (H.toValue $ toUrl filePath)
    $ H.toHtml tag

renderPoleLink :: String -> Maybe FilePath -> Maybe H.Html
renderPoleLink _ Nothing = Nothing
renderPoleLink pole (Just filePath) =
  Just
    $ H.a
      H.! A.title (H.stringValue $ "Page du pôle « " ++ (poleName M.! pole) ++ " »")
      H.! A.class_ "tag is-light has-text-dark"
      H.! A.href (H.toValue $ toUrl filePath)
    $ H.toHtml (poleName M.! pole)

makeTags :: [H.Html] -> H.Html
makeTags tags = H.div H.! A.class_ "tags mt-2" $ (mconcat . intersperse "\n") tags

-- Utility functions
pluralize :: Int -> String -> String
pluralize count str = case count of
  1 -> show count ++ " " ++ str
  _ -> show count ++ " " ++ str ++ "s"

abstract :: Pandoc -> Maybe [Inline]
abstract (Pandoc _ blocks) =
  markedUp blocks <|> fallback blocks
  where
    markedUp = fmap getFirst . query $ \case
      Span (_, cls, _) inls | "abstract" `elem` cls -> First (Just inls)
      _ -> mempty
    fallback (Para inlines : Header {} : _) = Just inlines
    fallback (_ : t) = fallback t
    fallback [] = Nothing

customPandocCompiler :: Compiler (Item String)
customPandocCompiler =
  pandocCompilerWithTransformM
    defaultHakyllReaderOptions
    defaultHakyllWriterOptions {writerHTMLMathMethod = MathML}
    ( \pandoc -> do
        let render = fmap writePandoc . makeItem . Pandoc mempty . pure . Plain
        maybe
          (pure ())
          (void . (saveSnapshot "abstract" <=< render))
          (abstract pandoc)
        pure $ addSectionLinks pandoc
    )
    >>= relativizeUrls

addSectionLinks :: Pandoc -> Pandoc
addSectionLinks = walk f
  where
    f (Header n attr@(idAttr, _, _) inlines) =
      let link = Link ("", [""], []) [Str "§"] ("#" <> idAttr, "")
       in Header n attr ([link, Space] <> inlines)
    f x = x

snapshotField :: String -> Snapshot -> Context String
snapshotField key snap = field key $ \item ->
  loadSnapshotBody (itemIdentifier item) snap

context :: Context String
context =
  mapContext escapeHtml metadataField
    <> snapshotField "abstract" "abstract"
    <> defaultContext

postCtx :: Context String
postCtx =
  dateField "date" "%d/%m/%Y"
    <> context

postCtxWith :: Tags -> Tags -> Tags -> Context String
postCtxWith tags authors poles =
  tagsFieldWith (getTagsByField "poles") renderPoleLink makeTags "poles" poles
    <> tagsFieldWith (getTagsByField "authors") simpleRenderLink (mconcat . (\l -> if null l then [] else H.toHtml ("par " :: String) : l) . intersperse ", ") "author" authors
    <> tagsFieldWith (getTagsByField "authors") licenseRenderLink (mconcat . intersperse ", " . (\l -> if null l then [] else l ++ [H.toHtml ("and" :: String)])) "authorLicense" authors
    <> tagsFieldWith getTags renderTagLink makeTags "tags" tags
    <> postCtx

simpleRenderLink :: String -> Maybe FilePath -> Maybe H.Html
simpleRenderLink _ Nothing = Nothing
simpleRenderLink tag (Just filePath) =
  Just $ H.a H.! A.href (H.toValue $ toUrl filePath) $ H.toHtml tag

licenseRenderLink :: String -> Maybe FilePath -> Maybe H.Html
licenseRenderLink _ Nothing = Nothing
licenseRenderLink tag (Just filePath) =
  Just $ H.a H.! A.href (H.toValue $ toUrl filePath) H.! A.rel "cc:attributionURL dct:creator" $ H.toHtml tag
