-- tabset.lua
-- Copyright (C) 2020-2022 Posit Software, PBC
local card = require 'modules/dashboard/card'
local cardToolbar = require 'modules/dashboard/card-toolbar'
local utils = require 'modules/dashboard/utils'

-- Tabset classes
local kTabsetClass = "tabset"
local kTabClass = "tab"

local kTabOutputClass = "card"
local kTabBodyClass = "card-body"
local kTabHeaderClass = "card-header"
local kTabFooterClass = "card-footer"

local kTitleAttr = "title"
local kTabAttributes = {kTitleAttr}

local kTabTitleOutAttr = "data-title"

-- pill vs tabs vs underline
-- longer term, could emit a menu that goes with a card or a tabbed card

local function resolveTabHeader(_titleEl, options) 
  if options[kTitleAttr] ~= nil then
    -- Use the explicit title
    return pandoc.Div(options[kTitleAttr], pandoc.Attr("", {kTabHeaderClass}))
  else
    -- Couldn't find a title, but force the header into place
    return pandoc.Div(pandoc.Plain(""), pandoc.Attr("", {kTabHeaderClass}))
  end
end

local function resolveTabFooter(tabFooterEls) 
  if tabFooterEls and #tabFooterEls > 0 then
    return pandoc.Div(tabFooterEls, pandoc.Attr("", {kTabFooterClass}))
  end
end

local function resolveTabs(contents)
  local tabFooterEls = pandoc.List()
  local tabBodyEls = pandoc.List()
  local tabHeaderEls = pandoc.List()
  local ignoreEls = pandoc.List()

  -- Process the contents (figuring out the tab title, dealing with cards, etc...)
  for _i, v in ipairs(contents) do   

    local tabContent = v
    local attr = {}

    if card.isCard(v) then
      -- If the direct descendent of a tab is a card, just hoist the card contents
      -- up into the tab body and forward along the title and footer
      attr[kTabTitleOutAttr] = v.attributes[kTabTitleOutAttr]

      local cardTabContents = pandoc.List()
      _quarto.ast.walk(v.content, {
        Div = function(divEl)
          if card.isCardBody(divEl) then
            cardTabContents:extend(divEl.content)
          elseif card.isCardFooter(divEl) then
            tabFooterEls:extend(divEl.content)
          end
        end
      })
      tabContent = cardTabContents
    elseif cardToolbar.isCardToolbar(v) then 
      -- If an input panel is captured by a tabset, it will naturally go into the
      -- the header for the tabset - place it and mark it processed
      tabHeaderEls:insert(v)
      cardToolbar.markProcessed(v)
      tabContent = nil

    elseif v.classes ~= nil and v.classes:includes('hidden') then
      ignoreEls:insert(v)
      tabContent = nil
    else
      -- If the direct descrendent of a tab isn't a card, see if it has a header within it
      -- that we can use as the tab title, then just allow the content to flow along
      if v.content and #v.content > 1 then
        if v.content[1].t == "Header" then
          local titleAttr = v.content[1].attributes[kTitleAttr]
          if titleAttr ~= nil then
            attr[kTabTitleOutAttr] = titleAttr
          else
            attr[kTabTitleOutAttr] = pandoc.utils.stringify(v.content[1])
          end   
        end
      end 
    end
    
    if tabContent ~= nil then
      tabBodyEls:insert(pandoc.Div(tabContent, pandoc.Attr("", {kTabBodyClass}, attr)))
    end
  end

  return tabBodyEls, tabHeaderEls, tabFooterEls, ignoreEls
end



local function makeTabset(title, contents, classes, options)  

  -- compute the card contents
  local tabContents = pandoc.List({})

  -- the card header
  local tabHeader = resolveTabHeader(title, options)
  
  if tabHeader ~= nil then
    tabContents:insert(tabHeader)  
  end

  -- compute the card body(ies)
  local tabEls, tabHeaderEls, tabFooterEls, ignoreEls = resolveTabs(contents)
  tabContents:extend(tabEls)

  -- add anything to header that needs to be added
  if tabHeaderEls ~= nil and tabHeader ~= nil then
    tprepend(tabHeader.content, tabHeaderEls)
  end

  -- compute any card footers
  local tabFooter = resolveTabFooter(tabFooterEls)
  if tabFooter ~= nil then
    tabContents:insert(tabFooter)
  end

  -- place any other stuff there too
  if ignoreEls ~= nil then
    tabContents:extend(ignoreEls)
  end


  -- add outer classes
  local clz = pandoc.List({kTabOutputClass, kTabsetClass})
  if classes then
    clz:extend(classes)
  end

  -- forward options onto attributes
  local attributes = {}
  options = options or {}  
  for k,v in pairs(options) do
    attributes['data-' .. k] = pandoc.utils.stringify(v)
  end
  
  local tabEl = pandoc.Div(tabContents, pandoc.Attr("", clz, attributes))
  return tabEl

end

local function isTabset(el)
  return (is_regular_node(el, "Div") or el.t == "Header") and el.classes ~= nil and el.classes:includes(kTabsetClass)
end

local function readOptions(el)

  local options = {}
  
  if el.attributes ~= nil then
    for _i, v in ipairs(kTabAttributes) do
      if el.attributes[v] ~= nil then
        options[v] = el.attributes[v]
      end
    end
  end

  local clz = pandoc.List()
  if el.classes ~= nil then
    clz = el.classes:filter(function(class)
      -- strip the tabset class
      return class ~= kTabsetClass
    end)  
  end
  
  return options, clz

end

local function isTabHeader(el)
  return is_regular_node(el, "Div") and el.classes ~= nil and el.classes:includes(kTabHeaderClass)
end

local function isTabFooter(el)
  return is_regular_node(el, "Div") and el.classes ~= nil and el.classes:includes(kTabFooterClass)
end

local function isTabBody(el)
  return is_regular_node(el, "Div") and el.classes ~= nil and el.classes:includes(kTabBodyClass)
end

function addToHeader(tabset, content, title)
  local tabsetHeader = utils.findChildDiv(tabset, isTabHeader)
  if tabsetHeader then
    if title ~= nil then
      tabsetHeader.content:insert(1, pandoc.Plain(title))
    end
    tabsetHeader.content:insert(content)
  else
    local headerContent = pandoc.List({content})
    if title ~= nil then
      headerContent:insert(1, pandoc.Plain(title))
    end
    local newHeader = pandoc.Div(headerContent, pandoc.Attr("", {kTabHeaderClass}))
    tabset.content:insert(1, newHeader)
  end
end

function addToFooter(tabset, content)
  local tabsetFooter = utils.findChildDiv(tabset, isTabFooter)
  if tabsetFooter then
    tabsetFooter.content:insert(content)
  else
    local newFooter = pandoc.Div(content, pandoc.Attr("", {kTabFooterClass}))
    tabset.content:insert(newFooter)
  end
end

function addSidebar(tabset, content)
  tabset.content:insert(1, content)
end

return {
  isTabset = isTabset,
  readOptions = readOptions,
  makeTabset = makeTabset,
  addToHeader = addToHeader,
  addToFooter = addToFooter,
  addSidebar = addSidebar
}

