-- 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.

--
-- 文件名: render_banxin.lua  - 版心（鱼尾）绘制模块
-- 层级: 第三阶段 - 渲染层 (Stage 3: Render Layer)
--
-- 【模块功能 / Module Purpose】
-- 本模块负责绘制古籍排版中的"版心"（中间的分隔列），包括：
--   1. 绘制版心列的边框（与普通列边框样式相同）
--   2. 在版心内绘制两条水平分隔线，将版心分为三个区域
--   3. 在版心第一区域绘制竖排文字（鱼尾文字，如书名、卷号等）
--   4. 支持自定义三个区域的高度比例（默认 0.28:0.56:0.16）
--
-- 【整体架构】
--   draw_banxin_column(p_head, params)
--      ├─ render_border() - 绘制边框
--      ├─ draw_banxin() - 绘制分隔线和鱼尾
--      ├─ render_book_name() - 绘制书名文字
--      ├─ render_chapter_title() - 绘制章节标题
--      ├─ render_page_number() - 绘制页码
--      ├─ render_publisher() - 绘制出版社/刊号
--      └─ render_debug_rects() - 调试矩形
--
-- ============================================================================

-- Load dependencies
local constants = package.loaded['core.luatex-cn-constants'] or
    require('core.luatex-cn-constants')
local D = constants.D
local utils = package.loaded['util.luatex-cn-utils'] or
    require('util.luatex-cn-utils')
local text_position = package.loaded['core.luatex-cn-render-position'] or
    require('luatex-cn-render-position')
local yuwei = package.loaded['banxin.luatex-cn-banxin-render-yuwei'] or require('banxin.luatex-cn-banxin-render-yuwei')
local banxin_layout = package.loaded['banxin.luatex-cn-banxin-layout'] or require('banxin.luatex-cn-banxin-layout')

-- Register banxin module if debug module is available
local debug = package.loaded['debug.luatex-cn-debug'] or
    require('debug.luatex-cn-debug')

local dbg = debug.get_debugger('banxin')

-- Conversion factor from scaled points to PDF big points
local sp_to_bp = utils.sp_to_bp

-- ============================================================================
-- Helper Functions (纯函数，只计算不产生副作用)
-- ============================================================================

-- Reuse shared helpers from banxin-layout module
local count_utf8_chars = banxin_layout.count_utf8_chars
local calculate_yuwei_dimensions = banxin_layout.calculate_yuwei_dimensions
local calculate_yuwei_total_height = banxin_layout.calculate_yuwei_total_height

--- 将节点链插入到链表头部
-- @param p_head (node) 当前链表头
-- @param glyph_chain (node) 要插入的节点链
-- @return (node) 新的链表头
local function prepend_chain(p_head, glyph_chain)
    if not glyph_chain then return p_head end
    local chain_tail = glyph_chain
    while D.getnext(chain_tail) do
        chain_tail = D.getnext(chain_tail)
    end
    D.setlink(chain_tail, p_head)
    return glyph_chain
end

-- ============================================================================
-- PDF Literal Generators (生成 PDF 指令字符串)
-- ============================================================================

--- 生成边框矩形的 PDF literal
-- @param x (number) X 坐标 (sp)
-- @param y (number) Y 坐标 (sp)
-- @param width (number) 宽度 (sp)
-- @param height (number) 高度 (sp, 正值)
-- @param thickness (number) 线宽 (sp)
-- @param color_str (string) RGB 颜色字符串
-- @return (string) PDF literal 字符串
local function create_border_literal(x, y, width, height, thickness, color_str)
    local x_bp = x * sp_to_bp
    local y_bp = y * sp_to_bp
    local width_bp = width * sp_to_bp
    local height_bp = -height * sp_to_bp -- Negative because Y goes downward
    local thickness_bp = thickness * sp_to_bp

    return string.format(
        "q %.2f w %s RG %.4f %.4f %.4f %.4f re S Q",
        thickness_bp, color_str, x_bp, y_bp, width_bp, height_bp
    )
end

--- 生成水平分隔线的 PDF literal
-- @param x (number) X 坐标 (sp)
-- @param y (number) Y 坐标 (sp)
-- @param width (number) 宽度 (sp)
-- @param thickness (number) 线宽 (sp)
-- @param color_str (string) RGB 颜色字符串
-- @return (string) PDF literal 字符串
local function create_divider_literal(x, y, width, thickness, color_str)
    local x_bp = x * sp_to_bp
    local y_bp = y * sp_to_bp
    local width_bp = width * sp_to_bp
    local thickness_bp = thickness * sp_to_bp

    return string.format(
        "q %.2f w %s RG %.4f %.4f m %.4f %.4f l S Q",
        thickness_bp, color_str,
        x_bp, y_bp,
        x_bp + width_bp, y_bp
    )
end

-- ============================================================================
-- Node Creation Functions (创建节点)
-- ============================================================================

--- 创建 PDF literal 节点
-- @param literal_str (string) PDF literal 字符串
-- @return (node) PDF literal 节点 (direct node)
local function create_literal_node(literal_str)
    local n = node.new("whatsit", "pdf_literal")
    n.data = literal_str
    n.mode = 0
    return D.todirect(n)
end

-- ============================================================================
-- Draw Banxin Internals (版心内部绘制)
-- ============================================================================

--- 绘制版心分隔线
-- @param x (number) X 坐标 (sp)
-- @param y (number) Y 坐标 (sp, 顶边缘)
-- @param width (number) 宽度 (sp)
-- @param upper_height (number) 上区域高度 (sp)
-- @param middle_height (number) 中区域高度 (sp)
-- @param thickness (number) 线宽 (sp)
-- @param color_str (string) RGB 颜色字符串
-- @return (table) PDF literal 字符串数组
local function draw_dividers(x, y, width, upper_height, middle_height, thickness, color_str)
    local div1_y = y - upper_height
    local div2_y = div1_y - middle_height

    return {
        create_divider_literal(x, div1_y, width, thickness, color_str),
        create_divider_literal(x, div2_y, width, thickness, color_str),
    }
end

--- 绘制上鱼尾
-- @param x (number) X 坐标 (sp)
-- @param div1_y (number) 第一分隔线 Y 坐标 (sp)
-- @param width (number) 宽度 (sp)
-- @param yuwei_dims (table) 鱼尾尺寸
-- @param color_str (string) RGB 颜色字符串
-- @param thickness (number) 线宽 (sp)
-- @return (string) PDF literal 字符串
local function draw_upper_yuwei(x, div1_y, width, yuwei_dims, color_str, thickness)
    local yuwei_y = div1_y - yuwei_dims.gap
    return yuwei.draw_yuwei({
        x = x,
        y = yuwei_y,
        width = width,
        edge_height = yuwei_dims.edge_height,
        notch_height = yuwei_dims.notch_height,
        style = "black",
        direction = 1, -- Notch at bottom (上鱼尾)
        color_str = color_str,
        extra_line = true,
        border_thickness = thickness,
    })
end

--- 绘制下鱼尾
-- @param x (number) X 坐标 (sp)
-- @param div2_y (number) 第二分隔线 Y 坐标 (sp)
-- @param width (number) 宽度 (sp)
-- @param yuwei_dims (table) 鱼尾尺寸
-- @param color_str (string) RGB 颜色字符串
-- @param thickness (number) 线宽 (sp)
-- @return (string) PDF literal 字符串
local function draw_lower_yuwei(x, div2_y, width, yuwei_dims, color_str, thickness)
    local yuwei_y = div2_y + yuwei_dims.notch_height + yuwei_dims.gap
    return yuwei.draw_yuwei({
        x = x,
        y = yuwei_y,
        width = width,
        edge_height = yuwei_dims.edge_height,
        notch_height = yuwei_dims.notch_height,
        style = "black",
        direction = -1, -- Notch at top (下鱼尾)
        color_str = color_str,
        extra_line = true,
        border_thickness = thickness,
    })
end

--- 绘制版心的分隔线和鱼尾
-- @param params (table) 绘制参数
-- @return (table) { literals: string[], upper_height: number }
local function draw_banxin(params)
    local x = params.x or 0
    local y = params.y or 0
    local width = params.width or 0
    local total_height = params.total_height or 0
    local r1 = params.upper_ratio or 0.28
    local r2 = params.middle_ratio or 0.56
    local color_str = params.color_str or "0 0 0"
    local thickness = params.border_thickness or 26214
    local upper_height = total_height * r1
    local middle_height = total_height * r2

    local literals = {}

    -- Draw dividers
    if params.banxin_divider ~= false then
        local divider_literals = draw_dividers(x, y, width, upper_height, middle_height, thickness, color_str)
        for _, lit in ipairs(divider_literals) do
            table.insert(literals, lit)
        end
    end

    -- Draw yuwei
    local yuwei_dims = calculate_yuwei_dimensions(width)
    local div1_y = y - upper_height
    local div2_y = div1_y - middle_height

    if params.upper_yuwei ~= false then
        table.insert(literals, draw_upper_yuwei(x, div1_y, width, yuwei_dims, color_str, thickness))
    end

    local lower_yuwei_enabled = params.lower_yuwei
    if lower_yuwei_enabled == nil then lower_yuwei_enabled = true end
    if lower_yuwei_enabled then
        table.insert(literals, draw_lower_yuwei(x, div2_y, width, yuwei_dims, color_str, thickness))
    end

    return {
        literals = literals,
        upper_height = upper_height,
    }
end

-- ============================================================================
-- Text Rendering Functions (文字渲染)
-- ============================================================================

-- Forward declaration
local create_vertical_text

--- 创建竖向排列的文字链
-- 将字符按从上到下的顺序排列在单列中。
create_vertical_text = function(text, params)
    if not text or text == "" then
        return nil
    end

    -- Parse UTF-8 characters
    local chars = {}
    for char in text:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
        table.insert(chars, char)
    end

    local num_chars = #chars
    if num_chars == 0 then
        return nil
    end

    local x = params.x or 0
    local y_top = params.y_top or 0
    local width = params.width or 0
    local height = params.height or 0
    local num_cells = params.num_cells or num_chars
    local v_align = params.v_align or "center"
    local h_align = params.h_align or "center"
    local font_id = params.font_id or font.current()
    local shift_y = params.shift_y or 0

    local font_scale_factor = 1.0
    local base_font_data = font.getfont(font_id)

    -- Handle font size if provided
    if params.font_size then
        local fs = constants.to_dimen(params.font_size)
        if fs and fs > 0 then
            local current_font_data = font.getfont(font_id)
            if current_font_data then
                font_scale_factor = fs / current_font_data.size
                local new_font_data = {}
                for k, v in pairs(current_font_data) do new_font_data[k] = v end
                new_font_data.size = fs
                font_id = font.define(new_font_data)
            end
        end
    elseif params.font_scale then
        font_scale_factor = params.font_scale
        local current_font_data = font.getfont(font_id)
        if current_font_data then
            local new_font_data = {}
            for k, v in pairs(current_font_data) do new_font_data[k] = v end
            new_font_data.size = math.floor(new_font_data.size * params.font_scale + 0.5)
            font_id = font.define(new_font_data)
        end
    end

    -- Calculate cell height
    local cell_height = height / num_cells

    local head = nil
    local tail = nil

    for i, char in ipairs(chars) do
        -- Create glyph node
        local glyph = node.new(node.id("glyph"))
        glyph.char = utf8.codepoint(char)
        glyph.font = font_id
        glyph.lang = 0

        local glyph_direct = D.todirect(glyph)

        -- Fetch glyph dimensions
        local cp = utf8.codepoint(char)
        local gw = (base_font_data and base_font_data.size or (65536 * 10)) * font_scale_factor
        local gh = gw * 0.8
        local gd = gw * 0.2

        if base_font_data and base_font_data.characters and base_font_data.characters[cp] then
            local char_data = base_font_data.characters[cp]
            gw = (char_data.width or gw) * font_scale_factor
            gh = (char_data.height or gh) * font_scale_factor
            gd = (char_data.depth or gd) * font_scale_factor
            D.setfield(glyph_direct, "width", math.floor(gw + 0.5))
            D.setfield(glyph_direct, "height", math.floor(gh + 0.5))
            D.setfield(glyph_direct, "depth", math.floor(gd + 0.5))
        end

        local row = i - 1
        local cell_y = y_top - row * cell_height - shift_y

        -- Position the glyph using core utility
        local _, kern = text_position.position_glyph(glyph_direct, x, cell_y, {
            cell_width = width,
            cell_height = cell_height,
            h_align = h_align,
            v_align = v_align,
            g_width = math.floor(gw + 0.5),
            g_height = math.floor(gh + 0.5),
            g_depth = math.floor(gd + 0.5),
        })

        if head == nil then
            head = glyph_direct
            tail = kern
        else
            D.setlink(tail, glyph_direct)
            tail = kern
        end

        -- Debug rects
        if dbg.is_enabled() then
            if utils and utils.draw_debug_rect then
                head = utils.draw_debug_rect(head, glyph_direct, x, cell_y, width, -cell_height, "0 0 1 RG")
            end
        end
    end

    return head
end

--- Generic function to render a text section
-- Consolidates the common pattern used by book_name, chapter, page_number, publisher
-- @param p_head (node) Current list head
-- @param layout (table|nil) Layout params with: text, x, y_top, width, height, v_align, h_align, font_size
-- @return (node) Updated list head
local function render_text_section(p_head, layout)
    if not layout or not layout.text or layout.text == "" then
        return p_head
    end
    local chain = create_vertical_text(layout.text, {
        x = layout.x,
        y_top = layout.y_top,
        width = layout.width,
        height = layout.height,
        num_cells = layout.num_cells,
        v_align = layout.v_align or "center",
        h_align = layout.h_align or "center",
        font_size = layout.font_size,
        font_scale = layout.font_scale,
    })
    if chain then
        p_head = prepend_chain(p_head, chain)
    end
    return p_head
end

-- Reuse parse_chapter_title from banxin-layout module
local parse_chapter_title = banxin_layout.parse_chapter_title

-- ============================================================================
-- Debug Functions (调试功能)
-- ============================================================================

--- 渲染调试矩形
-- @param p_head (node) 当前链表头
-- @param x, y, width, height (number) 矩形位置和尺寸 (sp)
-- @return (node) 新的链表头
local function render_debug_rects(p_head, x, y, width, height)
    if not (dbg.is_enabled()) then
        return p_head
    end

    p_head = utils.draw_debug_rect(p_head, nil, x, y, width, -height, "0 1 0 RG [2 2] 0 d")
    p_head = utils.draw_debug_rect(p_head, nil, x, y, width, -height, "1 0 0 RG")

    return p_head
end

-- ============================================================================
-- Layout-Based Rendering Functions (布局驱动渲染)
-- ============================================================================

--- Render chapter title from layout data with runtime content
-- @param p_head (node) Node list head
-- @param element (table) Chapter title layout element
-- @param chapter_title (string) Runtime chapter title content
-- @return (node) Updated node list head
local function render_chapter_title_from_layout(p_head, element, chapter_title)
    if not chapter_title or chapter_title == "" then return p_head end

    local parts = parse_chapter_title(chapter_title)
    if #parts == 0 then return p_head end

    local n_cols = math.max(#parts, element.n_cols or 1)
    local col_width = element.width / n_cols

    for i, sub_text in ipairs(parts) do
        local c = i - 1
        local sub_x = element.x + (n_cols - 1 - c) * col_width

        local col_h_align = element.h_align or "center"
        if n_cols > 1 then
            if i == 1 then
                col_h_align = "right"
            elseif i == #parts then
                col_h_align = "left"
            end
        end

        local num_chars = count_utf8_chars(sub_text)
        local total_h = num_chars * element.grid_height

        p_head = render_text_section(p_head, {
            text = sub_text,
            x = sub_x,
            y_top = element.y_top,
            width = col_width,
            height = total_h,
            v_align = "center",
            h_align = col_h_align,
            font_size = element.font_size,
            font_scale = element.font_scale,
        })
    end

    return p_head
end

--- Render page number from layout data with runtime content
-- @param p_head (node) Node list head
-- @param element (table) Page number layout element
-- @param page_number (number) Runtime page number
-- @return (node) Updated node list head
local function render_page_number_from_layout(p_head, element, page_number, explicit_page_number)
    if not page_number and not explicit_page_number then return p_head end

    -- Use explicit page number string if provided (digital mode),
    -- otherwise auto-convert numeric page number to Chinese numeral
    local page_str = explicit_page_number or utils.to_chinese_numeral(page_number)
    if page_str == "" then return p_head end

    local num_chars = count_utf8_chars(page_str)
    local container_height = element.grid_height * num_chars

    -- Recalculate y_top based on actual character count
    local page_y_top
    if element.v_align == "center" then
        local available_middle_h = element.middle_height - element.upper_yuwei_total - element.lower_yuwei_total
        local center_y = element.middle_y_bottom + element.lower_yuwei_total + available_middle_h / 2
        page_y_top = center_y + container_height / 2
    else
        page_y_top = element.middle_y_bottom + element.lower_yuwei_total + element.page_bottom_margin + container_height
    end

    p_head = render_text_section(p_head, {
        text = page_str,
        x = element.x,
        y_top = page_y_top,
        width = element.width,
        height = container_height,
        v_align = element.v_align,
        h_align = element.h_align,
        font_size = element.font_size,
    })

    return p_head
end

--- Draw banxin column from pre-calculated layout
-- This function uses layout data calculated in the layout stage
-- and resolves runtime content (page number, chapter title) at render time.
-- @param p_head (node) Node list head
-- @param layout (table) Pre-calculated layout from banxin-layout module
-- @param runtime (table) Runtime content { chapter_title, page_number }
-- @return (node) Updated node list head
local function draw_from_layout(p_head, layout, runtime)
    local col = layout.column
    local decorations = layout.decorations
    local regions = layout.regions

    -- 1. Draw border
    if col.draw_border then
        local border_literal = create_border_literal(
            col.x, col.y, col.width, col.height,
            col.border_thickness, col.color_str
        )
        p_head = D.insert_before(p_head, p_head, create_literal_node(border_literal))
    end

    -- 2. Draw dividers
    if decorations.draw_dividers then
        local divider_literals = draw_dividers(
            col.x, col.y, col.width,
            regions.upper.height, regions.middle.height,
            col.border_thickness, col.color_str
        )
        for _, lit in ipairs(divider_literals) do
            p_head = D.insert_before(p_head, p_head, create_literal_node(lit))
        end
    end

    -- 3. Draw fish tails (yuwei)
    local div1_y = col.y - regions.upper.height
    local div2_y = div1_y - regions.middle.height

    if decorations.upper_yuwei then
        local upper_lit = draw_upper_yuwei(
            col.x, div1_y, col.width,
            decorations.yuwei_dims, col.color_str, col.border_thickness
        )
        p_head = D.insert_before(p_head, p_head, create_literal_node(upper_lit))
    end

    if decorations.lower_yuwei then
        local lower_lit = draw_lower_yuwei(
            col.x, div2_y, col.width,
            decorations.yuwei_dims, col.color_str, col.border_thickness
        )
        p_head = D.insert_before(p_head, p_head, create_literal_node(lower_lit))
    end

    -- 4. Render text elements
    for _, element in ipairs(layout.elements) do
        if element.type == "book_name" then
            p_head = render_text_section(p_head, element)
        elseif element.type == "chapter_title" then
            p_head = render_chapter_title_from_layout(p_head, element, runtime.chapter_title)
        elseif element.type == "page_number" then
            p_head = render_page_number_from_layout(p_head, element, runtime.page_number, runtime.explicit_page_number)
        elseif element.type == "publisher" then
            p_head = render_text_section(p_head, element)
        end
    end

    -- 5. Debug rectangles
    p_head = render_debug_rects(p_head, col.x, col.y, col.width, col.height)

    return p_head
end

-- ============================================================================
-- Module Export
-- ============================================================================

local banxin = {
    draw_banxin = draw_banxin,
    draw_from_layout = draw_from_layout,
    -- Internal functions exported for testing
    _internal = {
        count_utf8_chars = count_utf8_chars,
        calculate_yuwei_dimensions = calculate_yuwei_dimensions,
        calculate_yuwei_total_height = calculate_yuwei_total_height,
        create_border_literal = create_border_literal,
        create_divider_literal = create_divider_literal,
        parse_chapter_title = parse_chapter_title,
    },
}

package.loaded['banxin.luatex-cn-banxin-render-banxin'] = banxin

return banxin
