{-# LANGUAGE LambdaCase           #-}
{-# LANGUAGE OverloadedStrings    #-}
{- |
   Module      : Text.Pandoc.Lua.Marshal.Chunks
   Copyright   : © 2022 Albert Krewinkel
   License     : GPL-2.0-or-later
   Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>

Marshaling chunks, i.e., pandoc subdocuments.
-}
module Text.Pandoc.Lua.Marshal.Chunks
  ( peekChunk
  , pushChunk
  , peekChunkedDoc
  , pushChunkedDoc
  ) where

import Control.Monad ((<$!>))
import Data.Tree (Tree (..))
import HsLua
import Text.Pandoc.Chunks (Chunk (..), ChunkedDoc (..), SecInfo (..))
import Text.Pandoc.Lua.Marshal.AST

-- | Retrieves a 'Chunk' from the Lua stack.
peekChunk :: LuaError e => Peeker e Chunk
peekChunk = peekUD typeChunk

-- | Pushes a 'Chunk' to the top of the Lua stack.
pushChunk :: LuaError e => Pusher e Chunk
pushChunk = pushUD typeChunk

typeChunk :: LuaError e => DocumentedType e Chunk
typeChunk = deftype "Chunk"
  [ operation Tostring $ lambda
    ### liftPure show
    <#> udparam typeChunk "chunk" "chunk to print in native format"
    =#> functionResult pushString "string" "Haskell representation"
  ]
  [ property "heading"
    "heading text"
    (pushInlines, chunkHeading)
    (peekInlinesFuzzy, \chunk inlns -> chunk{ chunkHeading = inlns })

  , property "id"
    "identifier"
    (pushText, chunkId)
    (peekText, \chunk ident -> chunk{ chunkId = ident })

  , property "level"
    "level of topmost heading in chunk"
    (pushIntegral, chunkLevel)
    (peekIntegral, \chunk level -> chunk{ chunkLevel = level })

  , property "number"
    "chunk number"
    (pushIntegral, chunkNumber)
    (peekIntegral, \chunk number -> chunk{ chunkNumber = number })

  , property "section_number"
    "hierarchical section number"
    (pushMaybe pushText, chunkSectionNumber)
    (peekMaybe peekText, \chunk secnum -> chunk{ chunkSectionNumber = secnum })

  , property "path"
    "target filepath for this chunk"
    (pushString, chunkPath)
    (peekString, \chunk path -> chunk{ chunkPath = path })

  , property "up"
    "link to the enclosing section chunk, if any"
    (pushMaybe pushChunk, chunkUp)
    (peekMaybe peekChunk, \chunk up -> chunk{ chunkUp = up })

  , property "prev"
    "link to the previous section, if any"
    (pushMaybe pushChunk, chunkPrev)
    (peekMaybe peekChunk, \chunk prev -> chunk{ chunkPrev = prev })

  , property "next"
    "link to the next section, if any"
    (pushMaybe pushChunk, chunkNext)
    (peekMaybe peekChunk, \chunk next' -> chunk{ chunkNext = next' })

  , property "unlisted"
    ( "whether the section in this chunk should be listed in the TOC" <>
      "even if the chunk has no section number" )
    (pushBool, chunkUnlisted)
    (peekBool, \chunk unlisted -> chunk { chunkUnlisted = unlisted })

  , property "contents"
    "the chunk's block contents"
    (pushBlocks, chunkContents)
    (peekBlocksFuzzy, \chunk blks -> chunk{ chunkContents = blks })
  ]

-- | Retrieves a 'ChunkedDoc' from the Lua stack.
peekChunkedDoc :: LuaError e => Peeker e ChunkedDoc
peekChunkedDoc = peekUD typeChunkedDoc

-- | Pushes a 'ChunkedDoc to the top of the Lua stack.
pushChunkedDoc :: LuaError e => Pusher e ChunkedDoc
pushChunkedDoc = pushUD typeChunkedDoc

-- | Lua type for 'ChunkedDoc' values.
typeChunkedDoc :: LuaError e => DocumentedType e ChunkedDoc
typeChunkedDoc = deftype "ChunkedDoc"
  []
  [ readonly "chunks"
    "list of chunks that make up the document"
    (pushList pushChunk, chunkedChunks)

  , readonly "meta"
    "the document's metadata"
    (pushMeta, chunkedMeta)

  , readonly "toc"
    "table of contents information"
    (pushTocTree, chunkedTOC)
  ]

-- | Pushes a TOC tree to the stack. The resulting object is a list with
-- the top-level entry placed at index @0@ and all subentries as the
-- rest of the list.
pushTocTree :: LuaError e => Pusher e (Tree SecInfo)
pushTocTree (Node secInfo subSecInfo) = do
  pushList pushTocTree subSecInfo
  pushSecInfo secInfo
  rawseti (nth 2) 0

pushSecInfo :: LuaError e => Pusher e SecInfo
pushSecInfo = pushAsTable
  [ ("title"  , pushInlines . secTitle)
  , ("number" , maybe pushnil pushText . secNumber)
  , ("id"     , pushText . secId)
  , ("path"   , pushText . secPath)
  , ("level"  , pushIntegral . secLevel)
  ]

peekMaybe :: LuaError e => Peeker e a -> Peeker e (Maybe a)
peekMaybe p idx = liftLua (isnoneornil idx) >>= \case
  True  -> pure Nothing
  False -> Just <$!> p idx

pushMaybe :: LuaError e => Pusher e a -> Pusher e (Maybe a)
pushMaybe = maybe pushnil
