-- Put a Div with class "landscape" on one A4 landscape page.
-- Use either ::: {.landscape} around the float and its note, or
-- #| classes: landscape on the chunk. Do not use both: Quarto would
-- otherwise see two nested landscape divs.
--
-- This filter runs at pre-quarto and strips the class before Quarto's
-- own landscape div filter. That built-in inserts an empty sectPr for
-- every such div; stacked with a second mark it leaves blank pages.
-- If the note is outside the div, the following paragraphs that start
-- with 注 or 资料来源 are pulled into the same section.
--
-- sectPr describes the section that ends at that paragraph.
-- Footer id defaults to rId14 (page-number footer in the bundled
-- reference doc). Override with YAML metadata docx-landscape-footer
-- when a project reference doc uses a different relationship id.

local FOOTER_ID = "rId14"

local function apply_footer_id(doc)
  if doc.meta == nil then
    return
  end
  local meta = doc.meta["docx-landscape-footer"]
  if meta == nil then
    return
  end
  local s = pandoc.utils.stringify(meta)
  if s ~= nil and s ~= "" then
    FOOTER_ID = s
  end
end
local MARGIN = "1418"
local HF = "851"

local function is_docx()
  if quarto and quarto.doc and quarto.doc.is_format then
    local ok, res = pcall(function()
      return quarto.doc.is_format("docx")
    end)
    if ok and res then
      return true
    end
  end
  return FORMAT ~= nil and tostring(FORMAT):match("docx") ~= nil
end

local function sect_para(width, height, orient)
  local orient_attr = ""
  if orient ~= nil and orient ~= "" then
    orient_attr = ' w:orient="' .. orient .. '"'
  end
  local xml = table.concat({
    '<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"',
    ' xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">',
    "<w:pPr><w:sectPr>",
    '<w:footerReference w:type="default" r:id="' .. FOOTER_ID .. '"/>',
    '<w:type w:val="nextPage"/>',
    '<w:pgSz w:w="' .. width .. '" w:h="' .. height .. '"' .. orient_attr .. "/>",
    '<w:pgMar w:top="' .. MARGIN .. '" w:right="' .. MARGIN .. '"',
    ' w:bottom="' .. MARGIN .. '" w:left="' .. MARGIN .. '"',
    ' w:header="' .. HF .. '" w:footer="' .. HF .. '" w:gutter="0"/>',
    '<w:cols w:space="720"/>',
    "</w:sectPr></w:pPr></w:p>",
  })
  return pandoc.RawBlock("openxml", xml)
end

local function close_portrait()
  return sect_para("11906", "16838", nil)
end

local function close_landscape()
  return sect_para("16838", "11906", "landscape")
end

local function is_ast(el)
  local kind = type(el)
  return kind == "table" or kind == "userdata"
end

local function has_class(el, name)
  if not is_ast(el) or el.classes == nil then
    return false
  end
  local ok, found = pcall(function()
    return el.classes:includes(name)
  end)
  return ok and found == true
end

local function strip_landscape_class(el)
  if not is_ast(el) or el.t == nil then
    return
  end
  if el.classes ~= nil then
    local kept = pandoc.List()
    for _, class_name in ipairs(el.classes) do
      if class_name ~= "landscape" then
        kept:insert(class_name)
      end
    end
    el.classes = kept
  end
  if el.content ~= nil then
    for _, child in ipairs(el.content) do
      strip_landscape_class(child)
    end
  end
end

local function is_landscape_block(el)
  return has_class(el, "landscape")
end

local function plain_start(el)
  if el.t ~= "Para" and el.t ~= "Plain" then
    return ""
  end
  local s = pandoc.utils.stringify(el)
  return (s:gsub("^%s+", ""))
end

local function is_note_block(el)
  local s = plain_start(el)
  return s:match("^注") ~= nil or s:match("^资料来源") ~= nil
end

local function is_gap_block(el)
  if el.t == "RawBlock" then
    local t = el.text or ""
    return #t > 0 and #t < 40
  end
  if el.t == "Para" or el.t == "Plain" then
    return plain_start(el) == ""
  end
  return false
end

local function is_float_body(el)
  if el.t == "Table" or el.t == "Figure" then
    return true
  end
  if el.t == "Para" or el.t == "Plain" then
    if el.content == nil then
      return false
    end
    for _, child in ipairs(el.content) do
      if child.t == "Image" then
        return true
      end
    end
  end
  return false
end

local function walk_blocks(blocks, inside)
  local out = pandoc.List()
  local i = 1
  while i <= #blocks do
    local b = blocks[i]
    if is_landscape_block(b) and not inside then
      strip_landscape_class(b)
      local inner = pandoc.List()
      if b.t == "Div" and b.content ~= nil then
        inner = walk_blocks(b.content, true)
      else
        inner:insert(b)
      end
      out:insert(close_portrait())
      for _, block in ipairs(inner) do
        out:insert(block)
      end
      i = i + 1
      while i <= #blocks and is_float_body(blocks[i]) do
        out:insert(blocks[i])
        i = i + 1
      end
      while i <= #blocks do
        local n = blocks[i]
        if is_note_block(n) then
          out:insert(n)
          i = i + 1
        elseif is_gap_block(n) and blocks[i + 1] ~= nil and is_note_block(blocks[i + 1]) then
          out:insert(n)
          i = i + 1
        else
          break
        end
      end
      out:insert(close_landscape())
    elseif b.t == "Div" and b.content ~= nil then
      b.content = walk_blocks(b.content, inside)
      out:insert(b)
      i = i + 1
    else
      out:insert(b)
      i = i + 1
    end
  end
  return out
end

function Pandoc(doc)
  if not is_docx() then
    return doc
  end
  apply_footer_id(doc)
  doc.blocks = walk_blocks(doc.blocks)
  return doc
end
