-- Copyright 2026 Open-Guji (https://github.com/open-guji)
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- ============================================================================
-- luatex-cn-core-table.lua - Table mode support
-- ============================================================================
--
-- Tables are inline sections within BodyText that use band (分栏) mode.
-- \begin{表格} emits PENALTY_TABLE_START, \end{表格} emits PENALTY_TABLE_END.
-- The layout engine dynamically switches to band mode upon TABLE_START and
-- restores single-band mode upon TABLE_END.
--
-- IMPORTANT: TeX processes \begin{表格}...\end{表格} sequentially during
-- paragraph building, but the layout engine runs later in post_linebreak_filter.
-- When multiple tables exist in the same paragraph, all init()/cleanup() calls
-- execute before any TABLE_START/TABLE_END penalty is processed by layout.
-- Therefore we use a FIFO queue (_G.content.table_instances) to store each
-- table's parameters. The layout engine dequeues from the front on TABLE_START.
-- ============================================================================

local constants = require('core.luatex-cn-constants')

local table_mod = {}

--- Initialize table mode and emit TABLE_START penalty
-- Called at \begin{Table} start
-- @param params table with n_bands, band_gap_sp, band_heights (optional)
function table_mod.init(params)
    _G.content = _G.content or {}
    _G.content.table_mode = true

    -- Create a new table instance and push onto the queue
    local instance = {
        params = params or {},
        col_groups = {},
        band_formats = {},
        cell_valigns = {},
        cell_column_borders = {},
        cell_idx = 0,
        cur_band = 0,
    }

    _G.content.table_instances = _G.content.table_instances or {}
    _G.content.table_instances[#_G.content.table_instances + 1] = instance

    -- Point current-instance fields to the latest instance for TeX-time writes
    _G.content.table_params = instance.params
    _G.content.table_col_groups = instance.col_groups
    _G.content.table_band_formats = instance.band_formats
    _G.content.table_cell_valigns = instance.cell_valigns
    _G.content.table_cell_column_borders = instance.cell_column_borders
    _G.content.table_cell_idx = instance.cell_idx
    _G.content.table_cur_band = instance.cur_band
    _G.content.table_render_cell_idx = 0

    local n = node.new("penalty")
    n.penalty = constants.PENALTY_TABLE_START
    node.write(n)
end

--- Clean up table mode and emit TABLE_END penalty
-- Called at \end{Table} end
function table_mod.cleanup()
    -- Snapshot current TeX-time state back into the active instance
    if _G.content and _G.content.table_instances then
        local instances = _G.content.table_instances
        local cur = instances[#instances]
        if cur then
            cur.col_groups = _G.content.table_col_groups
            cur.band_formats = _G.content.table_band_formats
            cur.cell_valigns = _G.content.table_cell_valigns
            cur.cell_column_borders = _G.content.table_cell_column_borders
        end
    end

    local n = node.new("penalty")
    n.penalty = constants.PENALTY_TABLE_END
    node.write(n)

    if _G.content then
        _G.content.table_mode = false
        _G.content.table_cell_idx = nil
        _G.content.table_render_cell_idx = nil
    end
end

--- Dequeue the next table instance (called by layout engine on TABLE_START)
-- Returns the instance table, or nil if queue is empty.
function table_mod.dequeue_instance()
    if not _G.content or not _G.content.table_instances then return nil end
    local instances = _G.content.table_instances
    if #instances == 0 then return nil end
    local instance = instances[1]
    table.remove(instances, 1)
    return instance
end

--- Register a cell and emit cell break penalty if needed
-- @param col_width (number) Number of columns this cell spans (0 = unlimited)
-- @param vertical_align (string|nil) Vertical alignment: "center", "top", "bottom"
-- @param column_border (boolean|nil) Per-cell column border override
local valign_alias = { ["居中"] = "center", ["居下"] = "bottom", ["居上"] = "top" }
function table_mod.begin_cell(col_width, vertical_align, column_border)
    _G.content = _G.content or {}
    local cell_idx = _G.content.table_cell_idx or 0
    local cur_band = _G.content.table_cur_band or 0

    -- Store per-band col_groups to avoid cross-band overwrites
    _G.content.table_col_groups = _G.content.table_col_groups or {}
    _G.content.table_col_groups[cur_band] = _G.content.table_col_groups[cur_band] or {}
    _G.content.table_col_groups[cur_band][cell_idx + 1] = col_width
    _G.content.table_cell_idx = cell_idx + 1

    vertical_align = valign_alias[vertical_align] or vertical_align
    if vertical_align then
        _G.content.table_cell_valigns = _G.content.table_cell_valigns or {}
        _G.content.table_cell_valigns[cur_band] = _G.content.table_cell_valigns[cur_band] or {}
        _G.content.table_cell_valigns[cur_band][cell_idx + 1] = vertical_align
    end

    if column_border ~= nil then
        _G.content.table_cell_column_borders = _G.content.table_cell_column_borders or {}
        _G.content.table_cell_column_borders[cur_band] = _G.content.table_cell_column_borders[cur_band] or {}
        _G.content.table_cell_column_borders[cur_band][cell_idx + 1] = column_border
    end

    if cell_idx > 0 then
        local n = node.new("penalty")
        n.penalty = constants.PENALTY_CELL_BREAK
        node.write(n)
    end
end

--- Reset cell counter for a new band (row)
-- Called by \换栏 command and by band break handler in layout-grid
function table_mod.reset_band_cells()
    if _G.content then
        _G.content.table_cell_idx = 0
        _G.content.table_cur_band = (_G.content.table_cur_band or 0) + 1
    end
end

--- Set format options for the current band
-- Called by \栏格式 / \BandFormat command
-- @param params table with optional column_border (bool)
function table_mod.set_band_format(params)
    _G.content = _G.content or {}
    local cur_band = _G.content.table_cur_band or 0
    _G.content.table_band_formats = _G.content.table_band_formats or {}
    _G.content.table_band_formats[cur_band] = params
end

--- Reset render cell index for a new band (row)
-- Called by band break handler in layout-grid
function table_mod.reset_render_band()
    if _G.content and _G.content.table_mode then
        _G.content.table_render_cell_idx = 0
    end
end

-- Register module
package.loaded['core.luatex-cn-core-table'] = table_mod

return table_mod
