{-# LANGUAGE OverloadedStrings #-}
{- |
   Module      : Text.Pandoc.Lua.Module.Template
   Copyright   : Copyright © 2022-2024 Albert Krewinkel, John MacFarlane
   License     : GNU GPL, version 2 or above
   Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>

Lua module to handle pandoc templates.
-}
module Text.Pandoc.Lua.Module.Template
  ( documentedModule
  ) where

import Data.Version (makeVersion)
import HsLua
import HsLua.Module.DocLayout (peekDoc, pushDoc)
import Text.Pandoc.Error (PandocError)
import Text.Pandoc.Lua.Marshal.AST (peekMeta, pushBlocks, pushInlines)
import Text.Pandoc.Lua.Marshal.Context (peekContext, pushContext)
import Text.Pandoc.Lua.Marshal.Template (typeTemplate, peekTemplate, pushTemplate)
import Text.Pandoc.Lua.PandocLua (PandocLua (unPandocLua), liftPandocLua)
import Text.Pandoc.Writers.Shared (metaToContext')
import Text.Pandoc.Templates
  ( compileTemplate, getDefaultTemplate, getTemplate, renderTemplate
  , runWithPartials, runWithDefaultPartials )

import qualified Data.Text as T

-- | The "pandoc.template" module.
documentedModule :: Module PandocError
documentedModule = Module
  { moduleName = "pandoc.template"
  , moduleDescription = T.unlines
    [ "Handle pandoc templates."
    ]
  , moduleFields = []
  , moduleOperations = []
  , moduleFunctions = functions
  , moduleTypeInitializers = [initType typeTemplate]
  }

-- | Template module functions.
functions :: [DocumentedFunction PandocError]
functions =
  [ defun "apply"
     ### liftPure2 renderTemplate
     <#> parameter peekTemplate "Template" "template" "template to apply"
     <#> parameter peekContext "table" "context" "variable values"
     =#> functionResult pushDoc "Doc" "rendered template"
     #? T.unlines
     [ "Applies a context with variable assignments to a template,"
     , "returning the rendered template. The `context` parameter must be a"
     , "table with variable names as keys and [[Doc]], string, boolean, or"
     , "table as values, where the table can be either be a list of the"
     , "aforementioned types, or a nested context."
     ]
    `since` makeVersion [3,0]

  , defun "compile"
     ### (\template mfilepath -> unPandocLua $
           case mfilepath of
             Just fp -> runWithPartials (compileTemplate fp template)
             Nothing -> runWithDefaultPartials
                        (compileTemplate "templates/default" template))
     <#> parameter peekText "string" "template" "template string"
     <#> opt (stringParam "templates_path"
              ("parameter to determine a default path and extension for " <>
               "partials; uses the data files templates path by default."))
     =#> functionResult (either failLua pushTemplate) "Template"
           "compiled template"
     #? T.unlines
     [ "Compiles a template string into a [[Template]] object usable by"
     , "pandoc."
     , ""
     , "If the `templates_path` parameter is specified, then it should be the"
     , "file path associated with the template. It is used when checking"
     , "for partials. Partials will be taken only from the default data"
     , "files if this parameter is omitted."
     , ""
     , "An error is raised if compilation fails."
     ]
    `since` makeVersion [2,17]

  , defun "default"
     ### (\mformat -> unPandocLua $ do
           let getFORMAT = liftPandocLua $ do
                 getglobal "FORMAT"
                 forcePeek $ peekText top `lastly` pop 1
           format <- maybe getFORMAT pure mformat
           getDefaultTemplate format)
     <#> opt (textParam "writer"
              ("name of the writer for which the template should be " <>
               "retrieved; defaults to the global `FORMAT`."))
     =#> functionResult pushText "string" "raw template"
    #? T.unlines
    [ "Returns the default template for a given writer as a string. An"
    , "error is thrown if no such template can be found."
    ]
    `since` makeVersion [2,17]

  , defun "get"
     ### (unPandocLua . getTemplate)
     <#> stringParam "filename" "name of the template"
     =#> textResult "content of template file"
     #? T.unlines
     [ "Retrieve text for a template."
     , ""
     , "This function first checks the resource paths for a file of this"
     , "name; if none is found, the `templates` directory in the user data"
     , "directory is checked.  Returns the content of the file, or throws"
     , "an error if no file is found."
     ]
    `since` makeVersion [3,2,1]

  , defun "meta_to_context"
     ### (\meta blockWriterIdx inlineWriterIdx -> unPandocLua $ do
             let blockWriter blks = liftPandocLua $ do
                   pushvalue blockWriterIdx
                   pushBlocks blks
                   callTrace 1 1
                   forcePeek $ peekDoc top
             let inlineWriter blks = liftPandocLua $ do
                   pushvalue inlineWriterIdx
                   pushInlines blks
                   callTrace 1 1
                   forcePeek $ peekDoc top
             metaToContext' blockWriter inlineWriter meta)
     <#> parameter peekMeta "Meta" "meta" "document metadata"
     <#> parameter pure "function" "blocks_writer"
           "converter from [[Blocks]] to [[Doc]] values"
     <#> parameter pure "function" "inlines_writer"
           "converter from [[Inlines]] to [[Doc]] values"
     =#> functionResult pushContext "table" "template context"
     #? T.unlines
     [ "Creates template context from the document's [[Meta]] data, using the"
     , "given functions to convert [[Blocks]] and [[Inlines]] to [[Doc]]"
     , "values."
     ]
    `since` makeVersion [3,0]
  ]
