-- 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.
-- ============================================================================
-- banxin_main.lua - 版心模块独立入口（注册钩子）
-- ============================================================================
-- 文件名: banxin_main.lua
-- 层级: 扩展层 (Extension Layer) - 古籍版心功能
--
-- 【模块功能 / Module Purpose】
-- 本模块作为 banxin 包的独立入口，负责：
--   1. 向 vertical.hooks 系统注册版心相关回调
--   2. 管理版心配置映射
--   3. 协调 banxin.render_banxin 和 banxin.render_yuwei 模块
--
-- 【设计原理】
-- banxin 作为一个可选插件，通过覆盖 vertical.hooks 接口实现其功能。
--
-- ============================================================================

-- Ensure core namespace exists (should be loaded by now)
_G.core = _G.core or {}
_G.core.hooks = _G.core.hooks or {}

-- Create banxin namespace for our modules
_G.banxin = _G.banxin or {}
_G.banxin.enabled = _G.banxin.enabled or false

--- Setup global banxin parameters from TeX
-- Called by \banxinSetup to pre-set banxin state
-- @param params (table) Parameters from TeX keyvals
local function banxin_setup(params)
    params = params or {}
    if params.enabled ~= nil then
        _G.banxin.enabled = (params.enabled == true or params.enabled == "true")
    end
end

-- Export setup function for early access
_G.banxin.setup = banxin_setup

local utils = package.loaded['util.luatex-cn-utils'] or
    require('util.luatex-cn-utils')
local debug = package.loaded['debug.luatex-cn-debug'] or
    require('debug.luatex-cn-debug')
local style_registry = package.loaded['util.luatex-cn-style-registry'] or
    require('util.luatex-cn-style-registry')
local D = node.direct

local dbg = debug.get_debugger('banxin')

-- =============================================================================
-- Direct TeX Variable Reading
-- =============================================================================
-- Read banxin parameters directly from TeX LaTeX3 variables
-- This eliminates the need to pass them through the params table

--- Read all banxin-related TeX variables
-- @return table A table containing all banxin configuration values
local function read_banxin_params()
    local get_tl = utils.get_tex_tl
    local get_bool = utils.get_tex_bool
    local get_int = utils.get_tex_int
    local parse_dim = utils.parse_dim_to_sp

    return {
        -- Layout ratios
        upper_ratio = tonumber(get_tl("l__luatexcn_banxin_upper_ratio_tl")) or 0.28,
        middle_ratio = tonumber(get_tl("l__luatexcn_banxin_middle_ratio_tl")) or 0.56,

        -- Padding (from book-name and page-number sub-namespaces)
        padding_top = parse_dim(get_tl("l__luatexcn_banxin_book_name_top_padding_tl")),
        padding_bottom = parse_dim(get_tl("l__luatexcn_banxin_page_number_bottom_padding_tl")),

        -- Divider
        divider = get_bool("l__luatexcn_banxin_divider_bool"),

        -- Book name
        book_name = get_tl("l__luatexcn_banxin_book_name_tl") or "",
        book_name_font_size = parse_dim(get_tl("l__luatexcn_banxin_book_name_font_size_tl")),
        book_name_grid_height = parse_dim(get_tl("l__luatexcn_banxin_book_name_grid_height_tl")),
        book_name_align = get_tl("l__luatexcn_banxin_book_name_align_tl") or "center",

        -- Chapter title
        chapter_title = get_tl("l__luatexcn_banxin_chapter_title_tl") or "",
        chapter_title_top_margin = parse_dim(get_tl("l__luatexcn_banxin_chapter_title_top_margin_tl")),
        chapter_title_cols = get_int("l__luatexcn_banxin_chapter_title_cols_int"),
        chapter_title_font_size = parse_dim(get_tl("l__luatexcn_banxin_chapter_title_font_size_tl")),
        chapter_title_grid_height = parse_dim(get_tl("l__luatexcn_banxin_chapter_title_grid_height_tl")),
        chapter_title_align = get_tl("l__luatexcn_banxin_chapter_title_align_tl") or "center",

        -- Yuwei
        upper_yuwei = get_bool("l__luatexcn_banxin_upper_yuwei_bool"),
        lower_yuwei = get_bool("l__luatexcn_banxin_lower_yuwei_bool"),

        -- Page number
        page_number_align = get_tl("l__luatexcn_banxin_page_number_align_tl") or "right-bottom",
        page_number_font_size = parse_dim(get_tl("l__luatexcn_banxin_page_number_font_size_tl")),
        page_number_grid_height = parse_dim(get_tl("l__luatexcn_banxin_page_number_grid_height_tl")),

        -- Publisher
        publisher = get_tl("l__luatexcn_banxin_publisher_tl") or "",
        publisher_font_size = parse_dim(get_tl("l__luatexcn_banxin_publisher_font_size_tl")),
        publisher_grid_height = parse_dim(get_tl("l__luatexcn_banxin_publisher_grid_height_tl")),
        publisher_bottom_margin = parse_dim(get_tl("l__luatexcn_banxin_publisher_bottom_margin_tl")),
        publisher_align = get_tl("l__luatexcn_banxin_publisher_align_tl") or "right",
    }
end

-- 1. Load sub-modules using full namespaced paths
local render_banxin = package.loaded['banxin.luatex-cn-banxin-render-banxin'] or
    require('banxin.luatex-cn-banxin-render-banxin')
local banxin_layout = package.loaded['banxin.luatex-cn-banxin-layout'] or
    require('banxin.luatex-cn-banxin-layout')

-- 2. Export module table for TeX/other modules
local banxin_main = {}

--- Initialize Banxin Plugin
-- Captures banxin parameters early for use in layout and render stages
function banxin_main.initialize(params, engine_ctx)
    -- Read banxin_on directly from TeX variable
    local banxin_on = utils.get_tex_bool("l__luatexcn_banxin_on_bool")
    -- Banxin is active if banxin_on is true or implicitly via n_column
    local is_active = banxin_on or (tonumber(params.n_column) or 0) > 0

    if not is_active then
        dbg.log("Initialized. Active=false")
        return { active = false }
    end

    -- Capture banxin parameters early (moved from render stage)
    local bp = read_banxin_params()

    dbg.log(string.format("Initialized. Active=true, book_name='%s'", bp.book_name or ""))

    return {
        active = true,
        params = bp,           -- Store captured parameters
        layout_cache = {},     -- Will store per-page layout data
    }
end

--- Flatten hook (Not used by banxin)
function banxin_main.flatten(nodes, engine_ctx, context)
    return nodes
end

--- Layout hook for Banxin
-- Calculates layout data for all banxin columns across all pages
-- and stores in context.layout_cache for the render stage
function banxin_main.layout(list, layout_map, engine_ctx, context)
    if not (context and context.active) then return end
    if engine_ctx.n_column <= 0 then return end

    local total_pages = engine_ctx.total_pages or 0
    local p_cols = engine_ctx.page_columns or 0
    local bp = context.params

    dbg.log(string.format("Layout hook: calculating for %d pages, %d cols/page", total_pages, p_cols))

    -- Calculate layout for each page's reserved columns
    for page_idx = 0, total_pages - 1 do
        local reserved_cols = engine_ctx.get_reserved_cols(page_idx, p_cols)

        context.layout_cache[page_idx] = {}

        for col = 0, p_cols - 1 do
            if reserved_cols[col] then
                local coords = engine_ctx.get_reserved_column_coords(col, p_cols)

                -- Prepare layout params from captured context params
                local b_padding_top = bp.padding_top > 0 and bp.padding_top or engine_ctx.b_padding_top
                local b_padding_bottom = bp.padding_bottom > 0 and bp.padding_bottom or engine_ctx.b_padding_bottom

                local layout_params = {
                    x = coords.x,
                    y = coords.y,
                    width = coords.width,
                    height = coords.height,
                    border_thickness = engine_ctx.border_thickness,
                    color_str = engine_ctx.border_rgb_str,
                    draw_border = engine_ctx.draw_border,
                    upper_ratio = bp.upper_ratio,
                    middle_ratio = bp.middle_ratio,
                    book_name = bp.book_name,
                    book_name_font_size = bp.book_name_font_size > 0 and bp.book_name_font_size or nil,
                    book_name_grid_height = bp.book_name_grid_height > 0 and bp.book_name_grid_height or nil,
                    book_name_align = bp.book_name_align,
                    b_padding_top = b_padding_top,
                    b_padding_bottom = b_padding_bottom,
                    upper_yuwei = bp.upper_yuwei,
                    lower_yuwei = bp.lower_yuwei,
                    banxin_divider = bp.divider,
                    chapter_title = bp.chapter_title,
                    chapter_title_top_margin = bp.chapter_title_top_margin > 0 and bp.chapter_title_top_margin or (65536 * 20),
                    chapter_title_cols = bp.chapter_title_cols > 0 and bp.chapter_title_cols or 1,
                    chapter_title_font_size = bp.chapter_title_font_size > 0 and bp.chapter_title_font_size or nil,
                    chapter_title_grid_height = bp.chapter_title_grid_height > 0 and bp.chapter_title_grid_height or nil,
                    chapter_title_align = bp.chapter_title_align,
                    page_number_align = bp.page_number_align,
                    page_number_font_size = bp.page_number_font_size > 0 and bp.page_number_font_size or nil,
                    publisher = bp.publisher,
                    publisher_font_size = bp.publisher_font_size > 0 and bp.publisher_font_size or nil,
                    publisher_grid_height = bp.publisher_grid_height > 0 and bp.publisher_grid_height or nil,
                    publisher_bottom_margin = bp.publisher_bottom_margin > 0 and bp.publisher_bottom_margin or nil,
                    publisher_align = bp.publisher_align,
                    -- Get font_size from style stack (falls back to default if not set)
                    font_size = style_registry.get_font_size(style_registry.current_id()) or 655360,
                }

                -- Calculate layout using the layout module
                local col_layout = banxin_layout.calculate_column_layout(layout_params)
                context.layout_cache[page_idx][col] = col_layout

                dbg.log(string.format("  Page %d, col %d: layout calculated", page_idx, col))
            end
        end
    end

    dbg.log(string.format("Layout hook completed: cached %d pages", total_pages))
end

--- Render hook for Banxin
-- Uses pre-calculated layout from layout stage and resolves runtime content
-- @param head (node) Page list head
-- @param layout_map (table) Main layout map
-- @param params (table) Render parameters
-- @param context (table) Plugin context (contains layout_cache)
-- @param page_idx (number) Current page index (0-based)
-- @param p_total_cols (number) Total columns on this page
function banxin_main.render(head, layout_map, params, context, engine_ctx, page_idx, _)
    if not (context and context.active) then return head end
    if engine_ctx.n_column <= 0 then return head end

    local page_layout = context.layout_cache and context.layout_cache[page_idx]
    if not page_layout then return head end

    local bp = context.params

    -- Resolve runtime content: chapter title (may vary per page)
    -- Only update when \chapter markers exist; walk back to find most recent marker.
    local chapter_title = bp.chapter_title
    local page_resets = engine_ctx.page_resets
    if page_resets and next(page_resets) and engine_ctx.page_chapter_titles then
        for p = page_idx, 0, -1 do
            if page_resets[p] then
                local t = engine_ctx.page_chapter_titles[p]
                if t and t ~= "" then
                    chapter_title = t
                end
                break
            end
        end
    end

    -- Resolve runtime content: page number
    -- Support explicit page number string (for digital mode)
    -- When \chapter resets page number, compute relative page number from last reset.
    local page_number
    if page_resets and next(page_resets) then
        local last_reset_page = -1
        for p = page_idx, 0, -1 do
            if page_resets[p] then
                last_reset_page = p
                break
            end
        end
        if last_reset_page >= 0 then
            page_number = 1 + (page_idx - last_reset_page)
        else
            page_number = (params.start_page_number or 1) + page_idx
        end
    else
        page_number = (params.start_page_number or 1) + page_idx
    end
    local explicit_page_number = _G.banxin and _G.banxin.explicit_page_number or nil

    local p_head = D.todirect(head)

    for _, col_layout in pairs(page_layout) do
        p_head = render_banxin.draw_from_layout(p_head, col_layout, {
            chapter_title = chapter_title,
            page_number = page_number,
            explicit_page_number = explicit_page_number,
        })
    end

    return D.tonode(p_head)
end

local banxin_plugin = {
    initialize = banxin_main.initialize,
    flatten = banxin_main.flatten,
    layout = banxin_main.layout,
    render = banxin_main.render,
    setup = banxin_setup,
}

-- Registry as plugin if core engine is present
if core and core.register_plugin then
    core.register_plugin("banxin", banxin_plugin)
end

package.loaded['banxin.luatex-cn-banxin-main'] = banxin_plugin

return banxin_plugin
