package com.yaoyicloud.factory;

import java.awt.image.BufferedImage;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;

import org.apache.poi.xwpf.usermodel.IBody;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;

import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.ByteArrayPictureRenderData;
import com.deepoove.poi.data.HyperlinkTextRenderData;
import com.deepoove.poi.data.PictureType;
import com.deepoove.poi.data.style.PictureStyle;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.ParagraphRenderPolicy;
import com.deepoove.poi.policy.PictureRenderPolicy;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.policy.TextRenderPolicy;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.TableTools;
import com.deepoove.poi.xwpf.BodyContainer;
import com.deepoove.poi.xwpf.BodyContainerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoyicloud.tools.DesensitizedUtils;
import com.yaoyicloud.tools.Util;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 抽象文档渲染器基类，提供DOCX模板渲染、格式处理及转换PDF的核心功能 主要功能： 1. 基于POI-TL的DOCX模板渲染 2. 文档格式处理（表格合并、空行清理等） 3. DOCX转HTML/PDF转换流水线 4. 多种表格渲染策略实现 设计特点： -
 * 采用模板方法模式，子类需实现getBasicPath()等抽象方法 - 内置多种表格渲染策略（循环行、合并单元格、统计计算等） - 支持图片、超链接等复杂元素渲染 - 提供数据脱敏处理能力
 */
@Slf4j
public abstract class AbstractRender {

    /*
    * 导出文件位置
    */
    protected final String cwd;

    /*
     * Docx结果文件路径
     */
    protected String docxResultPath;

    /*
     * html结果文件路径
     */
    protected String htmlResultPath;

    /*
     * pdf结果文件路径
     */
    protected String pdfResultPath;

    public AbstractRender(String cwd) {
        this.cwd = cwd;
    }

    /**
     * DOCX文档渲染核心方法 处理流程： 1. 创建输出目录 2. 编译模板并渲染数据 3. 保存渲染结果
     *
     * @param dataMap 模板数据Map
     * @param inputStream 模板文件流
     * @param builder 模板配置构建器
     * @param relationId 关联ID（用于生成文件名）
     * @param moduleType 模块类型（用于生成文件名）
     * @return 生成的DOCX文件路径
     */
    public String documentCompile(Map<String, Object> dataMap, InputStream inputStream,
        ConfigureBuilder builder, String relationId, String moduleType)
        throws IOException {

        // 注： 报告模板的模板变量按照json序列化的结果命名
        String basicPath = this.getBasicPath();
        String reportImagePath = this.getReportImagePath();
        String label = relationId + "_" + moduleType;
        // word导出位置
        String reportTempWordFile = basicPath + "/" + cwd + "/" + label + ".docx";
        // 新增：创建文件夹
        File parentDir = new File(basicPath + "/" + cwd);
        if (!parentDir.exists()) {
            boolean created = parentDir.mkdirs();
            if (!created) {
                throw new IOException("无法创建文件保存目录");
            }
        }
        Configure config = builder.build();
        XWPFTemplate template =
            XWPFTemplate.compile(inputStream, config).render(dataMap);
        template.writeToFile(reportTempWordFile);
        template.close();
        this.docxResultPath = reportTempWordFile;
        return this.docxResultPath;
    }

    /**
     * DOCX转PDF转换流水线（final方法，子类不可覆盖） 处理流程： 1. DOCX -> HTML 转换 2. HTML -> PDF 转换
     *
     * @return 生成的PDF文件路径
     */
    public final String fromDocxToPdf() throws IOException {
        // 转换 docxResultPath 为 html
        // 加工html
        // 把 html 文件转成 pdf
        fromDocxToHtml();
        this.pdfResultPath = fromHtmlToPdf();
        return this.pdfResultPath;
    }

    /**
     * DOCX转HTML转换方法（final方法，子类不可覆盖）
     * 
     * @return 生成的HTML文件路径
     */
    public final String fromDocxToHtml() throws IOException {
        // 转换 docxResultPath 为 html
        // 加工html
        this.htmlResultPath = cwd + "/1.html";
        return this.htmlResultPath;
    }

    /**
     * HTML转PDF转换方法（final方法，子类不可覆盖）
     * 
     * @return 生成的PDF文件路径
     */
    public final String fromHtmlToPdf() throws IOException {
        // 转换 htmlResultPath 为 pdf
        this.pdfResultPath = cwd + "/1.pdf";
        return this.pdfResultPath;
    }

    /**
     * Docx 渲染
     *
     * @param info 数据
     * @param relationId 关联ID
     * @return 当前章节数据map
     * @throws IOException
     */
    protected abstract Map<String, Object> renderDocx(String info, String relationId, Integer counter)
        throws IOException;

    /**
     * 获取基础路径
     *
     * @return 文件存储基础路径
     */
    protected abstract String getBasicPath() throws IOException;

    /**
     * 获取报告图片路径（需子类实现）
     *
     * @return 图片存储路径
     */
    protected abstract String getReportImagePath();

    protected abstract ConfigureBuilder builder(String relationId);

    Map<String, Object> sensitiveData(Map<String, Object> data, Boolean sensitiveFlag) {
        if (!sensitiveFlag) {
            return data;
        }
        // 第四章信息脱敏处理要处理的数据
        Set<String> listSet = new HashSet<>(Arrays.asList("customer_visit_list", "meeting_list",
            "task_type_51_52_53_list", "task_type_55_list", "task_type_56_57_list",
            "task_type8_11_list", "task_type_9_10_list", "policy_info_collect_list",
            "competing_products_info_collect_list", "inventory_info_collect_list",
            "task_type_16_list", "task_type_18_list", "task_type_15_list", "task_type45_list", "task_type74_list",
            "task_type_75_list", "user_tasks", "task_type_54_list",
            "task_type_76_list", "task_type_78_79_list", "task_type_80_list", "task_type_81_list", "task_type801_list",
            "task_type38_list", "task_type39_40_41_list",
            "task_type42_43_44_list", "task_type46_list", "task_type47_48_49_50_list", "task_type_19_list",
            "task_type_34_35_36_list", "tasks", "task_type_85_list",
            "task_type_86_list", "task_type_87_list"));
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            // 敏感数据处理
            String key = entry.getKey();
            // 处理C端数据封面姓名脱敏
            if ("task_users".equals(key)) {
                Object o = data.get("task_users");
                String userName = DesensitizedUtils.chineseName((String) o);
                data.put("task_users", userName);
                continue;
            }
            // 处理第五章的数据脱敏
            if (key.equals("task_promotioner_quality_5_4_1")) {
                Object o = data.get("task_promotioner_quality_5_4_1");
                List<Map<String, Object>> taskPromotionalQuality = (List<Map<String, Object>>) o;
                for (Map<String, Object> sensitiveObject : taskPromotionalQuality) {
                    if (!sensitiveObject.containsKey("promotioner")) {
                        continue;
                    }
                    Object object = sensitiveObject.get("promotioner");
                    String sensitiveName = DesensitizedUtils.chineseName((String) object);
                    sensitiveObject.put("promotioner", sensitiveName);
                }
            }
            // 处理第四章的数据脱敏
            if (!listSet.contains(key)) {
                continue;
            }
            Object tempKey = data.get(key);
            List<Map<String, Object>> temp = (List<Map<String, Object>>) tempKey;
            for (Map<String, Object> sensitiveObject : temp) {
                if (!sensitiveObject.containsKey("user_name")) {
                    continue;
                }
                Object o = sensitiveObject.get("user_name");
                if ("合计".equals(o)) {
                    continue;
                }
                String userName = DesensitizedUtils.chineseName((String) o);
                sensitiveObject.put("user_name", userName);
            }
        }
        return data;
    }

    /**
     * 列循环静态表格渲染策略（fxy 重要财务数据处理）
     *
     * 功能特点： - 支持在表格列中循环渲染数据 - 可配置前缀/后缀标记 - 支持反向渲染（从右向左）
     */
    public class LoopColumnStaticTableRenderPolicy implements RenderPolicy {

        private String prefix;
        private String suffix;
        private boolean onSameLine;
        private boolean reverse;
        private int valColIndex;

        public LoopColumnStaticTableRenderPolicy() {
            this(false);
        }

        public LoopColumnStaticTableRenderPolicy(boolean onSameLine) {
            this("[", "]", onSameLine);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix) {
            this(prefix, suffix, false);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine) {
            this(prefix, suffix, onSameLine, false);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse) {
            this(prefix, suffix, onSameLine, false, 1);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
            int valRowIndex) {
            this.prefix = prefix;
            this.suffix = suffix;
            this.onSameLine = onSameLine;
            this.reverse = reverse;
            this.valColIndex = valRowIndex;
        }

        @SuppressWarnings("checkstyle:NestedForDepth")
        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();
            try {
                if (!TableTools.isInsideTable(run)) {
                    throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
                }
                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                XWPFTable table = tagCell.getTableRow().getTable();
                run.setText("", 0);

                int templateColIndex = getTemplateColIndex(tagCell);
                // 模版变量列总是写在左边
                int minIndex = templateColIndex;
                int maxIndex = table.getRows().get(valColIndex).getTableCells().size() - 1;
                int currIndex = reverse ? maxIndex : minIndex;
                int indexDelta = reverse ? -1 : 1;

                // 目前expression当作数据Map的key来对待，将来可以当作POI-TL变量一致化的处理
                Map<Integer, String> idx2Expression = new HashMap<>();
                for (int i = 0; i < table.getRows().size(); i++) {
                    XWPFTableCell cell = table.getRows().get(i).getCell(templateColIndex);
                    String text = cell.getText().trim();
                    if (text.startsWith(prefix) && text.endsWith(suffix)) {
                        idx2Expression.put(i, text.substring(1, text.length() - 1));
                        cell.setText("");
                    }
                }

                int rowSize = table.getRows().size();
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;

                for (Map<String, Object> realData : mpData) {
                    for (int i = 0; i < rowSize; i++) {
                        if (!idx2Expression.containsKey(i)) {
                            continue;
                        }
                        XWPFTableRow row = table.getRow(i);
                        XWPFTableCell valueCell = row.getCell(currIndex);
                        String valStr = realData.getOrDefault(idx2Expression.get(i), "-").toString();
                        valueCell.setText(valStr);
                        for (XWPFParagraph paragraph : valueCell.getParagraphs()) {

                            for (XWPFRun runa : paragraph.getRuns()) {

                                runa.setFontFamily("思源黑体 Medium");

                                runa.setFontSize(9);
                            }
                        }
                    }
                    currIndex += indexDelta;
                }

            } catch (Exception e) {
                throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
            }
        }

        private int getTemplateColIndex(XWPFTableCell tagCell) {
            return onSameLine ? Util.getColIndexOfFirstRow(tagCell) : (Util.getColIndexOfFirstRow(tagCell) + 1);
        }
    }

    /**
     * 带统计功能的行循环表格渲染策略（fxy报告 1.3 得分情况特殊处理） 功能特点： - 自动计算平均值等统计值 - 支持百分数自动处理 - 合并首列相同内容单元格
     */
    protected RenderPolicy getScoreRenderPolicy() {
        RenderPolicy policy = new LoopRowTableRenderPolicy() {
            @SuppressWarnings("checkstyle:NestedForDepth")
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                // 检查数据是否为空
                if (data == null || (data instanceof Collection && ((Collection<?>) data).isEmpty())) {
                    // 数据为空时，删除整个表格
                    // removeTemplateTable(eleTemplate, template);
                    return;
                }

                super.render(eleTemplate, data, template);
                XWPFDocument doc = template.getXWPFDocument();
                for (XWPFTable table : doc.getTables()) {
                    boolean isTargetTable = false;
                    // 判断是否为目标表格：通过检查表格第一行是否包含货物明细相关表头关键词
                    List<String> headerKeywords = List.of("删除");
                    XWPFTableRow firstRow = table.getRow(0);
                    if (firstRow != null) {
                        for (XWPFTableCell cell : firstRow.getTableCells()) {
                            for (XWPFParagraph para : cell.getParagraphs()) {
                                for (XWPFRun run : para.getRuns()) {
                                    String text = run.text().trim();
                                    if (headerKeywords.contains(text)) {
                                        isTargetTable = true;
                                        break;
                                    }
                                }
                                if (isTargetTable) {
                                    break;
                                }
                            }
                            if (isTargetTable) {
                                break;
                            }
                        }
                    }
                    if (isTargetTable) {
                        // 删除表头（假设表头是第一行）
                        if (table.getRows().size() > 0) {
                            table.removeRow(0);
                        }
                        // 合并第一列相同内容单元格并居中
                        mergeAndCenterFirstColumn(table);
                    }
                }
            }

            private void mergeAndCenterFirstColumn(XWPFTable table) {
                if (table.getNumberOfRows() < 2) {
                    return;
                }

                int startRow = 0;
                String currentValue = getCellText(table.getRow(0)).get(0);

                for (int i = 1; i < table.getNumberOfRows(); i++) {
                    List<String> rowValues = getCellText(table.getRow(i));
                    if (rowValues.isEmpty()) {
                        continue;
                    }

                    String firstCellValue = rowValues.get(0);
                    if (firstCellValue.equals(currentValue)) {
                        // 继续查找相同值的行
                        continue;
                    } else {
                        // 合并从startRow到i-1行的第一列单元格
                        if (i - 1 > startRow) {
                            TableTools.mergeCellsVertically(table, 0, startRow, i - 1);
                            // 设置合并后单元格内容居中
                            setCellCenterAlignment(table.getRow(startRow).getCell(0));
                        }
                        startRow = i;
                        currentValue = firstCellValue;
                    }
                }

                // 处理最后一组相同值
                if (table.getNumberOfRows() - 1 > startRow) {
                    TableTools.mergeCellsVertically(table, 0, startRow, table.getNumberOfRows() - 1);
                    setCellCenterAlignment(table.getRow(startRow).getCell(0));
                }
            }
        };
        return policy;
    }

    /**
     * 行循环剪切合并表格策略
     *
     * 功能特点： - 保留表头两行 - 自动合并首列相同内容 - 支持按rank字段排序
     */
    public class LoopRowCutAndMergeFirstColTableRenderPolicy extends LoopRowTableRenderPolicy {

        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();
            XWPFTable table = null;
            try {
                if (!TableTools.isInsideTable(run)) {
                    throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
                }
                // Reserve the first two rows
                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                table = tagCell.getTableRow().getTable();
                for (int i = table.getNumberOfRows() - 1; i > 1; i--) {
                    table.removeRow(i);
                }
            } catch (Exception e) {
                throw new RenderException(
                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
            }

            // in case data not sorted by rank
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
            mpData.sort((a, b) -> Integer.valueOf(a.getOrDefault("rank", "0").toString())
                - Integer.valueOf(b.getOrDefault("rank", "0").toString()));
            for (Map<String, Object> map : mpData) {
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                    Object value = entry.getValue();
                    if (value != null && "".equals(value.toString())) {
                        entry.setValue("-");
                    }
                }
            }
            super.render(eleTemplate, mpData, template);

            try {
                // merge the first column
                Util.mergeFirstNColSimple(table, 1, 0);
            } catch (Exception e) {
                throw new RenderException(
                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
            }
        }
    }

    /**
     * 财务信息计算平均值的策略
     */
    public class LoopRowIncludeStatisticsTableRenderPolicy extends LoopRowTableRenderPolicy {

        private String valueTag;

        public LoopRowIncludeStatisticsTableRenderPolicy(String valueTag) {
            this.valueTag = valueTag;
        }

        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {

            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();

            XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
            XWPFTable table = tagCell.getTableRow().getTable();

            @SuppressWarnings("unchecked")
            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;

            for (Map<String, Object> row : mpData) {
                @SuppressWarnings("unchecked")
                List<String> values = (List<String>) row.get(valueTag);

                // 检查是否有百分号结尾的值
                boolean hasPercentage = values.stream().anyMatch(v -> v != null && v.endsWith("%"));

                // 处理百分号并转换为数值
                List<Double> processedValues = values.stream()
                    .filter(Objects::nonNull)
                    .map(v -> {
                        if (v.endsWith("%")) {
                            return v.substring(0, v.length() - 1);
                        }
                        return v;
                    })
                    .map(v -> {
                        try {
                            return Double.valueOf(v);
                        } catch (NumberFormatException e) {
                            return Double.NaN;
                        }
                    })
                    .filter(v -> !Double.isNaN(v))
                    .collect(Collectors.toList());

                // 计算平均值并保留两位小数
                double avg = processedValues.stream()
                    .mapToDouble(Double::doubleValue)
                    .average()
                    .orElse(Double.NaN);

                // 格式化结果
                String formattedAvg;
                if (Double.isNaN(avg)) {
                    formattedAvg = "-"; // 处理无有效数据的情况
                } else {
                    DecimalFormat df = new DecimalFormat("#0.00"); // 保留两位小数
                    formattedAvg = df.format(avg);
                    if (hasPercentage) {
                        formattedAvg += "%"; // 添加百分号
                    }

                }

                row.put("avg", formattedAvg);
            }

            super.render(eleTemplate, data, template);
            try {
                // merge the first column
                Util.mergeFirstNColSimple(table, 1, 0);
            } catch (Exception e) {
                throw new RenderException(
                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
            }
        }
    }

    /**
     * 没配置项，删除表格 配置了有数据正常显示，没数据显示暂无风险项
     */
    public RenderPolicy addDefaultRowForEmptyTable(Boolean itemFlag) {
        return new LoopRowTableRenderPolicy() {
            @SuppressWarnings("checkstyle:ReturnCount")
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                ArrayList<Map<String, String>> data1 = (ArrayList<Map<String, String>>) data;
                if (itemFlag) {
                    if (CollectionUtils.isNotEmpty(data1)) {
                        super.render(eleTemplate, data1, template);
                    } else {
                        RunTemplate runTemplate = (RunTemplate) eleTemplate;
                        XWPFRun run = runTemplate.getRun();
                        XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                        XWPFTable table = tagCell.getTableRow().getTable();
                        List<XWPFTableRow> rows = table.getRows();
                        if (rows == null || rows.size() <= 1) {
                            return;
                        }
                        int lastRowIndex = rows.size() - 1; // 最后一行的索引
                        for (int i = rows.size() - 2; i >= 0; i--) { // 从倒数第二行开始删除
                            table.removeRow(i);
                        }
                        // 获取最后一行（现在索引为0）
                        XWPFTableRow lastRow = table.getRow(0);
                        List<XWPFTableCell> cells = lastRow.getTableCells();
                        XWPFTableCell mergedCell = cells.get(0);
                        if (cells.size() > 1) {
                            // 设置跨列合并（gridSpan = 总列数）
                            mergedCell.getCTTc().addNewTcPr().addNewGridSpan().setVal(BigInteger.valueOf(cells.size()));
                            for (int i = cells.size() - 1; i > 0; i--) {
                                lastRow.removeCell(i);
                            }
                        }
                        // 设置合并后的内容和居中
                        mergedCell.setText("暂无风险项");
                        mergedCell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                        for (XWPFParagraph paragraph : mergedCell.getParagraphs()) {
                            paragraph.setAlignment(ParagraphAlignment.CENTER);
                            for (XWPFRun runa : paragraph.getRuns()) {
                                runa.setFontFamily("思源黑体 Medium");
                                runa.setFontSize(10);
                            }
                        }
                        try {
                            Util.mergeFirstNColSimple(table, 1, 0);
                        } catch (Exception e) {
                            throw new RenderException(
                                "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
                        }
                    }
                } else {
                    RunTemplate runTemplate = (RunTemplate) eleTemplate;
                    XWPFRun run = runTemplate.getRun();
                    XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                    XWPFTable table = tagCell.getTableRow().getTable();
                    XWPFDocument doc = template.getXWPFDocument();

                    List<XWPFTableRow> rows = table.getRows();
                    List<XWPFTableCell> tableCells = rows.get(0).getTableCells();
                    XWPFTableCell xwpfTableCell = tableCells.get(0);
                    String text = xwpfTableCell.getText();
                    String itemName = extractCollectionName(text);
                    int paragraphsToDelete = 1;
                    if (itemName.equals("sameaddress") || itemName.equals("samecontactNumberDatas")
                        || itemName.equals("sameExecutivesNames")) {
                        paragraphsToDelete = 2;
                    }
                    int tablePos = -1;
                    List<IBodyElement> bodyElements = doc.getBodyElements();
                    for (int i = 0; i < bodyElements.size(); i++) {
                        if (bodyElements.get(i) == table) {
                            tablePos = i;
                            break;
                        }
                    }
                    if (tablePos >= 0) {

                        // 4.1 先删除前一个段落（如果存在且是段落）
                        for (int i = 0; i < paragraphsToDelete && tablePos - 1 >= 0; i++) {
                            IBodyElement prevElement = doc.getBodyElements().get(tablePos - 1);
                            if (prevElement instanceof XWPFParagraph) {
                                doc.removeBodyElement(tablePos - 1);
                                tablePos--; // 每删除一个段落，表格位置前移
                            }
                        }

                        // 4.2 强制删除表格（通过操作底层 XML）
                        doc.removeBodyElement(tablePos);
                        doc.getTables();
                    }
                }
            }

        };
    }

    /**
     * Protobuf数据专用渲染策略
     *
     * 功能特点： - 自动处理空值为"-" - 支持从模板提取变量名, 并删除表格最后一行
     */
    protected RenderPolicy indicatorsRenderPolicyToProtobuf() {
        return new LoopRowTableRenderPolicy() {
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                // 获取模板中的变量如：[name]
                ArrayList<String> strings = processElement(eleTemplate);
                List<Map<String, Object>> processedData = null;
                if (null != data && data instanceof Iterable) {
                    processedData = (List<Map<String, Object>>) data;
                    processedData.forEach(map -> {
                        for (String string : strings) {
                            Object o = map.get(string);
                            if (null == o || "".equals(o)) {
                                map.put(string, "-");
                            }
                        }
                    });
                }
                // 调用父类渲染处理后的数据
                super.render(eleTemplate, processedData, template);
            }

        };
    }

    @SuppressWarnings("checkstyle:NestedForDepth")
    private ArrayList<String> processElement(ElementTemplate eleTemplate) {
        RunTemplate runTemplate = (RunTemplate) eleTemplate;
        XWPFRun run = runTemplate.getRun();
        XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
        XWPFTable table = tagCell.getTableRow().getTable();
        StringBuilder textBuilder = new StringBuilder();

        for (XWPFTableRow row : table.getRows()) {
            // 遍历行中的每一个单元格
            for (XWPFTableCell cell : row.getTableCells()) {
                // 提取单元格内的所有文本（包含段落、Run 等）
                for (XWPFParagraph para : cell.getParagraphs()) {
                    for (XWPFRun r : para.getRuns()) {
                        textBuilder.append(r.getText(0)); // 获取 Run 的文本
                    }
                }
                textBuilder.append(" ");
            }
            textBuilder.append(" ");
        }
        ArrayList<String> strings = new ArrayList<>();
        String string = textBuilder.toString();
        String[] split = string.split(" ");
        for (String s : split) {
            Pattern pattern = Pattern.compile("\\[(.*?)\\]");
            Matcher matcher = pattern.matcher(s);
            while (matcher.find()) {
                String contentInBracket = matcher.group(1);
                strings.add(contentInBracket);
            }
        }
        return strings;
    }

    /**
     * JSON数据专用渲染策略 功能特点： - 自动转换null值为"-" - 支持多层嵌套数据结构
     */
    protected RenderPolicy indicatorsRenderPolicy() {
        return new LoopRowTableRenderPolicy() {
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                // 处理数据中的null值
                Object processedData = processData(data);
                // 调用父类渲染处理后的数据
                super.render(eleTemplate, processedData, template);
            }

            private Object processData(Object data) {
                if (data == null) {
                    return null;
                }

                if (data instanceof List) {
                    // 处理List类型数据
                    return ((List<?>) data).stream()
                        .map(this::processItem)
                        .collect(Collectors.toList());
                }

                return data;
            }

            private Object processItem(Object item) {
                if (item == null) {
                    return "-";
                }

                // 如果元素是Map，处理Map中的null值
                if (item instanceof Map) {
                    Map<?, ?> map = (Map<?, ?>) item;
                    return map.entrySet().stream()
                        .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            e -> {
                                Object value = e.getValue();
                                // 若值为null或空字符串，转为"-"
                                if (value == null || "".equals(value)) {
                                    return "-";
                                }
                                return value;
                            }));
                }

                return item;
            }
        };
    }

    /**
     * 图片渲染策略 功能特点： - 支持PDF转图片嵌入 - 自动调整图片尺寸 - 支持多种图片格式
     */
    protected RenderPolicy pictureRenderPolicy() {
        return new PictureRenderPolicy() {
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                @SuppressWarnings("unchecked")
                Map<String, Object> mpData = (Map<String, Object>) data;
                String filename = mpData.getOrDefault("fileName", "").toString().trim();
                String url = mpData.getOrDefault("fileUri", "").toString();
                float targetWidth = 456.5f;

                if (StrUtil.isBlank(filename)) {
                    // uri render when no filename
                    TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
                        new HyperlinkTextRenderData(url, url));
                    return;
                } else if (filename.endsWith(".pdf")) {
                    // pdf render, replace data with bytestream
                    PDDocument document = null;
                    try {
                        document = Loader.loadPDF(new URL(url).openStream().readAllBytes());
                        PDFRenderer renderer = new PDFRenderer(document);

                        XWPFRun run = ((RunTemplate) eleTemplate).getRun();
                        BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(run);
                        // 每页一张图片
                        for (int i = 0; i < document.getNumberOfPages(); i++) {
                            // pdf 转 jpeg
                            BufferedImage image = renderer.renderImageWithDPI(i, 150);
                            ByteArrayOutputStream stream = new ByteArrayOutputStream();
                            ImageIO.write(image, "jpg", stream);

                            // 准备docx元素
                            if (i == 0) {
                                run.setText("", 0);
                            } else {
                                run = bodyContainer.insertNewParagraph(run).createRun();
                            }

                            // 准备POI-TL数据
                            ByteArrayPictureRenderData picData =
                                new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.JPEG);
                            Pair<Integer, Integer> targetSize =
                                calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
                            PictureStyle style = new PictureStyle();
                            style.setWidth(targetSize.getKey());
                            style.setHeight(targetSize.getValue());
                            picData.setPictureStyle(style);
                            Helper.renderPicture(run, picData);
                        }
                    } catch (Exception e) {
                        throw new RenderException(
                            "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
                    } finally {
                        if (document != null) {
                            try {
                                document.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } else {
                    BufferedImage image = null;
                    ByteArrayOutputStream stream = null;
                    try {
                        image = ImageIO.read(new URL(url));
                        stream = new ByteArrayOutputStream();
                        ImageIO.write(image, getFileExtension(filename), stream);
                    } catch (IOException e) {
                        throw new RenderException(
                            "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
                    }

                    ByteArrayPictureRenderData picData =
                        new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.suggestFileType(filename));
                    Pair<Integer, Integer> targetSize =
                        calculateTargetSize(image.getWidth(), image.getHeight(), targetWidth);
                    PictureStyle style = new PictureStyle();
                    style.setWidth(targetSize.getKey());
                    style.setHeight(targetSize.getValue());
                    picData.setPictureStyle(style);
                    super.render(eleTemplate, picData, template);
                }
            }

            /**
             * 计算目标尺寸（提取为内部方法）
             */
            private Pair<Integer, Integer> calculateTargetSize(int originWidth, int originHeight, float targetWidth) {
                float targetHeight = (originHeight * targetWidth) / originWidth;
                if (targetHeight > 645) {
                    targetHeight = 645;
                    targetWidth = (originWidth * targetHeight) / originHeight;
                }
                return Pair.of(Math.round(targetWidth), Math.round(targetHeight));
            }

            /**
             * 获取文件扩展名
             */
            private String getFileExtension(String filename) {
                int dotIndex = filename.lastIndexOf('.');
                return dotIndex > 0 ? filename.substring(dotIndex + 1).toLowerCase() : "jpg";
            }
        };
    }

    /**
     * 超链接渲染策略 功能特点： - 自动设置超链接样式 - 支持文本替换为链接
     */
    protected RenderPolicy hyperlinkRenderPolicy() {
        return new ParagraphRenderPolicy() {
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {

                if (data instanceof String) {
                    // 获取超链接URL（从数据中）
                    String url = data.toString();
                    if (StrUtil.isNotBlank(url)) {
                        TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
                            new HyperlinkTextRenderData(url, url));
                    }

                }
                if (data instanceof Map) {
                    Map<String, Object> mpData = (Map<String, Object>) data;
                    String url = mpData.getOrDefault("fileUri", "").toString();
                    TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
                        new HyperlinkTextRenderData(url, url));
                }

            }
        };
    }

    private List<String> getCellText(XWPFTableRow row) {
        List<String> texts = new ArrayList<>();
        if (row == null) {
            return texts;
        }
        for (XWPFTableCell cell : row.getTableCells()) {
            StringBuilder sb = new StringBuilder();
            for (XWPFParagraph para : cell.getParagraphs()) {
                for (XWPFRun run : para.getRuns()) {
                    sb.append(run.text());
                }
            }
            texts.add(sb.toString().trim());
        }
        return texts;
    }

    private void setCellCenterAlignment(XWPFTableCell cell) {
        CTTc cttc = cell.getCTTc();
        CTTcPr ctPr = cttc.addNewTcPr();
        ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
    }

    /**
     * 二级表头动态表格策略 功能特点： - 支持分类汇总 - 自动计算合计行 - 智能识别百分比/平均数等特殊列
     */
    protected DynamicTableRenderPolicy getTwoLevelTableDynamicPolicy() {
        return new DynamicTableRenderPolicy() {
            @SuppressWarnings("unchecked")
            @Override
            public void render(XWPFTable table, Object data) {
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, List<List<Object>>> keyedData = (Map<String, List<List<Object>>>) data;

                    // 起始行
                    int startRow = 1;

                    // Reserve the first and the last row
                    for (int i = table.getNumberOfRows() - 2; i >= startRow; i--) {
                        table.removeRow(i);
                    }

                    // Rows in middle
                    int baseNameSeq = 0;
                    for (Map.Entry<String, List<List<Object>>> entry : keyedData.entrySet()) {
                        baseNameSeq++;
                        String baseTypeName = entry.getKey();
                        List<List<Object>> rowList = entry.getValue();

                        // 当前大类
                        List<Object> summary = null;
                        for (int i = 0; i < rowList.size(); i++) {
                            List<Object> values = rowList.get(i);

                            XWPFTableRow addRow = table.insertNewTableRow(startRow);
                            if (i == 0) {
                                Util.setText(addRow.addNewTableCell(), Integer.toString(baseNameSeq));
                                Util.setText(addRow.addNewTableCell(), baseTypeName);
                            } else {
                                Util.setText(addRow.addNewTableCell(), "");
                                Util.setText(addRow.addNewTableCell(), "");
                            }
                            for (int j = 0; j < values.size(); j++) {
                                String text = getText(table, j, values.get(j));
                                Util.setText(addRow.addNewTableCell(), text);
                            }
                            startRow++;

                            // 统计summary
                            if (summary == null) {
                                summary = values.stream().collect(Collectors.toList());
                                continue;
                            }
                            List<Object> origin = summary;
                            summary = new ArrayList<>();
                            for (int j = 0; j < values.size(); j++) {
                                Object value = values.get(j);
                                String head = table.getRow(0).getCell(j + 2).getText();
                                if (isAvg(head)) {
                                    // calculate avg by hardcoding previous two columns
                                    Object divider = summary.get(j - 2) instanceof Set
                                        ? ((Set) summary.get(j - 2)).size() : origin.get(j - 2);
                                    summary.add(Long.valueOf(summary.get(j - 1).toString()) * 1d
                                        / Long.valueOf(divider.toString()));
                                    continue;
                                }
                                if (value instanceof Set) {
                                    ((Set<String>) origin.get(j)).addAll((Set<String>) value);
                                    summary.add(origin.get(j));
                                    continue;
                                }
                                if (value instanceof Long) {
                                    summary.add((Long) (origin.get(j)) + (Long) value);
                                } else if (value instanceof Integer) {
                                    summary.add((Integer) (origin.get(j)) + (Integer) value);
                                } else if (value instanceof Double) {
                                    summary.add((Double) (origin.get(j)) + (Double) value);
                                } else {
                                    summary.add("");
                                }
                            }
                        }
                        // section summary
                        if (rowList.size() > 0) {
                            XWPFTableRow addRow = table.insertNewTableRow(startRow);
                            Util.setText(addRow.addNewTableCell(), "");
                            Util.setText(addRow.addNewTableCell(), "");
                            Util.colorSummary(addRow.addNewTableCell(), "合计");
                            for (int j = 1; j < summary.size(); j++) {
                                Util.colorSummary(addRow.addNewTableCell(), getText(table, j, summary.get(j)));
                            }
                        }
                        startRow++;
                    }

                    // last row
                    XWPFTableRow lastRow = table.getRow(table.getNumberOfRows() - 1);
                    int numOfCol = 1;
                    while (lastRow.getCell(numOfCol) != null) {
                        numOfCol++;
                    }
                    List<List<Object>> values =
                        keyedData.values().stream().flatMap(List::stream).collect(Collectors.toList());
                    for (int i = 1; i < numOfCol; i++) {
                        XWPFTableCell cell = lastRow.getCell(i);
                        String head = table.getRow(0).getCell(i + 2).getText();
                        final int idx = i;
                        if (isPercentage(head)) {
                            Util.replaceLastRowText(cell, "100.00%");
                        } else if (values.size() > 0
                            && (values.get(0).get(i) instanceof Long || values.get(0).get(i) instanceof Integer)) {
                            Long value = values.stream().mapToLong(row -> Long.valueOf(row.get(idx).toString())).sum();
                            Util.replaceLastRowText(cell, value.toString());
                        } else if (values.size() > 0 && (values.get(0).get(i) instanceof Set)) {
                            Set<String> value = values.stream().map((row) -> (Set<String>) row.get(idx))
                                .reduce(new HashSet<String>(), (r, st) -> {
                                    r.addAll(st);
                                    return r;
                                });
                            Util.replaceLastRowText(cell, Integer.toString(value.size()));
                        }
                    }

                    // First column is seq number, the second is first category
                    Util.mergeFirstNCol(table, 2, 1);
                } catch (Exception e) {
                    log.error("渲染模板失败", e);
                    throw e;
                }

                table.getRows().forEach(row -> {
                    row.setHeight(1);
                });
            }

            @SuppressWarnings("unchecked")
            private String getText(XWPFTable table, int j, Object value) {
                String text = value.toString();
                if (value instanceof Double) {
                    text = String.format("%.2f", value);
                }
                String head = table.getRow(0).getCell(j + 2).getText();
                if (isPercentage(head)) {
                    text = String.format("%.2f", (Double) value * 100);
                    text += "%";
                }
                if (value instanceof Set) {
                    text = Integer.toString(((Set<String>) value).size());
                }
                return text;
            }

            private boolean isPercentage(String head) {
                return head.contains("占比") || head.contains("率");
            }

            private boolean isAvg(String head) {
                return head.contains("均");
            }
        };
    }

    /**
     * 末行动态表格策略
     *
     * 功能特点： - 仅处理表格最后一行 - 支持直接数据替换
     */
    protected DynamicTableRenderPolicy getTableLastRowDynamicPolicy() {
        return new DynamicTableRenderPolicy() {
            @Override
            public void render(XWPFTable table, Object data) {
                try {
                    @SuppressWarnings("unchecked")
                    List<Object> rowData = (List<Object>) data;
                    for (int i = 0; i < rowData.size(); i++) {
                        Object value = rowData.get(i);
                        if (value == null) {
                            continue;
                        }
                        XWPFTableCell cell = table.getRow(table.getNumberOfRows() - 1).getCell(i);
                        Util.replaceLastRowText(cell, value.toString());
                    }
                } catch (Exception e) {
                    log.error("渲染模板失败", e);
                    throw e;
                }
            }
        };
    }

    protected RenderPolicy getPictureListDelParagraphPolicy() {
        return new RenderPolicy() {
            @SuppressWarnings("checkstyle:ReturnCount")
            @Override
            public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
                RunTemplate runTemplate = (RunTemplate) eleTemplate;
                XWPFRun run = runTemplate.getRun();
                XWPFParagraph currentParagraph = (XWPFParagraph) run.getParent();

                // 检查当前内容是否为"是"
                if ("是".equals(data)) {
                    // 获取当前段落所在的文档对象
                    IBody body = currentParagraph.getBody();

                    // 处理文档主体中的段落
                    if (body instanceof XWPFDocument) {
                        XWPFDocument document = (XWPFDocument) body;
                        List<XWPFParagraph> paragraphs = document.getParagraphs();
                        int currentIndex = paragraphs.indexOf(currentParagraph);

                        // 确保存在上一个段落
                        if (currentIndex > 0) {
                            // 删除上一个段落
                            document.removeBodyElement(currentIndex - 1);
                            document.removeBodyElement(currentIndex - 1);
                        }
                    }
                }
            }
        };
    }

    /**
     * 动态表格渲染策略工厂方法（支持数据脱敏） 功能特点： 1. 支持按用户ID分组渲染表格数据 2. 自动计算分类汇总和小计 3. 支持敏感数据脱敏处理 4. 自动识别百分比、平均值等特殊列格式
     *
     * @param sensitiveFlag 是否启用敏感数据脱敏
     * @param userNameMap 用户ID到姓名的映射表
     * @return 配置好的动态表格渲染策略
     */
    protected DynamicTableRenderPolicy tableDynamicPolicy(Boolean sensitiveFlag, Map<Integer, String> userNameMap) {
        return new DynamicTableRenderPolicy() {
            @SuppressWarnings("unchecked")
            @Override
            public void render(XWPFTable table, Object data) {
                try {
                    Map<String, List<List<Object>>> keyedData = (Map<String, List<List<Object>>>) data;

                    // 保留表头(第0行)和表尾(最后一行)，清空中间行
                    int startRow = 1;
                    for (int i = table.getNumberOfRows() - 2; i >= startRow; i--) {
                        table.removeRow(i);
                    }

                    // 遍历每个用户的数据组
                    int baseNameSeq = 0;
                    for (Map.Entry<String, List<List<Object>>> entry : keyedData.entrySet()) {
                        baseNameSeq++;
                        String userIdKey = entry.getKey();

                        // 获取用户名（支持脱敏处理）
                        String baseTypeName = userNameMap.getOrDefault(Integer.valueOf(userIdKey), "无姓名");
                        if (sensitiveFlag) {
                            baseTypeName = chineseName(baseTypeName); // 姓名脱敏处理
                        }
                        List<List<Object>> rowList = entry.getValue();
                        // 当前用户组的汇总数据
                        List<Object> summary = null;

                        for (int i = 0; i < rowList.size(); i++) {
                            List<Object> values = rowList.get(i);

                            XWPFTableRow addRow = table.insertNewTableRow(startRow);
                            if (i == 0) {
                                Util.setText(addRow.addNewTableCell(), Integer.toString(baseNameSeq));
                                Util.setText(addRow.addNewTableCell(), baseTypeName);
                            } else {
                                Util.setText(addRow.addNewTableCell(), "");
                                Util.setText(addRow.addNewTableCell(), "");
                            }

                            // 填充数据列
                            for (int j = 0; j < values.size(); j++) {
                                String text = getText(table, j, values.get(j));
                                Util.setText(addRow.addNewTableCell(), text);
                            }
                            startRow++;

                            // 计算分类汇总
                            if (summary == null) {
                                summary = new ArrayList<>(values);
                                continue;
                            }

                            // 合并当前行数据到汇总
                            List<Object> origin = summary;
                            summary = new ArrayList<>();
                            for (int j = 0; j < values.size(); j++) {
                                Object value = values.get(j);
                                String head = table.getRow(0).getCell(j + 2).getText();

                                // 特殊处理平均值列
                                if (isAvg(head)) {
                                    Object divider = summary.get(j - 2) instanceof Set
                                        ? ((Set) summary.get(j - 2)).size()
                                        : origin.get(j - 2);
                                    summary.add(Long.valueOf(summary.get(j - 1).toString()) * 1d
                                        / Long.valueOf(divider.toString()));
                                    continue;
                                }

                                if (value instanceof Set) {
                                    ((Set<String>) origin.get(j)).addAll((Set<String>) value);
                                    summary.add(origin.get(j));
                                    continue;
                                }

                                // 数值类型累加
                                if (value instanceof Long) {
                                    summary.add((Long) (origin.get(j)) + (Long) value);
                                } else if (value instanceof Integer) {
                                    summary.add((Integer) (origin.get(j)) + (Integer) value);
                                } else if (value instanceof Double) {
                                    summary.add((Double) (origin.get(j)) + (Double) value);
                                } else {
                                    summary.add("");
                                }
                            }
                        }

                        if (rowList.size() > 0) {
                            XWPFTableRow addRow = table.insertNewTableRow(startRow);
                            Util.setText(addRow.addNewTableCell(), "");
                            Util.setText(addRow.addNewTableCell(), "");
                            Util.colorSummary(addRow.addNewTableCell(), "合计");

                            for (int j = 1; j < summary.size(); j++) {
                                Util.colorSummary(addRow.addNewTableCell(), getText(table, j, summary.get(j)));
                            }
                        }
                        startRow++;
                    }

                    // 处理表尾汇总行
                    XWPFTableRow lastRow = table.getRow(table.getNumberOfRows() - 1);
                    int numOfCol = 1;
                    while (lastRow.getCell(numOfCol) != null) {
                        numOfCol++;
                    }

                    // 计算全局汇总数据
                    List<List<Object>> values = keyedData.values().stream()
                        .flatMap(List::stream)
                        .collect(Collectors.toList());

                    for (int i = 1; i < numOfCol; i++) {
                        XWPFTableCell cell = lastRow.getCell(i);
                        String head = table.getRow(0).getCell(i + 2).getText();
                        final int idx = i;

                        // 百分比列特殊处理
                        if (isPercentage(head)) {
                            Util.replaceLastRowText(cell, "100.00%");
                        } else if (values.size() > 0
                            && (values.get(0).get(i) instanceof Long || values.get(0).get(i) instanceof Integer)) {
                            Long value = values.stream()
                                .mapToLong(row -> Long.valueOf(row.get(idx).toString()))
                                .sum();
                            Util.replaceLastRowText(cell, value.toString());
                        } else if (values.size() > 0 && (values.get(0).get(i) instanceof Set)) {
                            Set<String> value = values.stream()
                                .map((row) -> (Set<String>) row.get(idx))
                                .reduce(new HashSet<String>(), (r, st) -> {
                                    r.addAll(st);
                                    return r;
                                });
                            Util.replaceLastRowText(cell, Integer.toString(value.size()));
                        }
                    }

                    // 合并序号和名称列（垂直方向）
                    Util.mergeFirstNCol(table, 2, 1);

                } catch (Exception e) {
                    log.error("渲染模板失败", e);
                    throw e;
                }

                // 统一设置行高
                table.getRows().forEach(row -> {
                    row.setHeight(1);
                });
            }

            /**
             * 格式化单元格文本 特殊处理： 1. Double类型保留2位小数 2. 百分比列自动添加%符号 3. Set类型显示元素数量
             */
            @SuppressWarnings("unchecked")
            private String getText(XWPFTable table, int j, Object value) {
                String text = value.toString();

                // 数字格式化
                if (value instanceof Double) {
                    text = String.format("%.2f", value);
                }

                // 检查列头是否包含百分比标识
                String head = table.getRow(0).getCell(j + 2).getText();
                if (isPercentage(head)) {
                    text = String.format("%.2f", (Double) value * 100);
                    text += "%";
                }

                // 集合类型显示元素数
                if (value instanceof Set) {
                    text = Integer.toString(((Set<String>) value).size());
                }

                return text;
            }

            /**
             * 判断是否是百分比列
             */
            private boolean isPercentage(String head) {
                return head.contains("占比") || head.contains("率");
            }

            /**
             * 判断是否是平均值列
             */
            private boolean isAvg(String head) {
                return head.contains("均");
            }
        };
    }

    /**
     * 【中文姓名】只显示最后一个汉字，其他隐藏为星号，比如：**梦
     * 
     * @param fullName 姓名
     * @return 结果
     */
    public static String chineseName(String fullName) {
        if (fullName == null) {
            return null;
        }
        return desValue(fullName, 0, 1, "*");
    }

    /**
     * 对字符串进行脱敏操作
     * 
     * @param origin 原始字符串
     * @param prefixNoMaskLen 左侧需要保留几位明文字段
     * @param suffixNoMaskLen 右侧需要保留几位明文字段
     * @param maskStr 用于遮罩的字符串, 如'*'
     * @return 脱敏后结果
     */
    public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
        if (origin == null) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0, n = origin.length(); i < n; i++) {
            if (i < prefixNoMaskLen) {
                sb.append(origin.charAt(i));
                continue;
            }
            if (i > (n - suffixNoMaskLen - 1)) {
                sb.append(origin.charAt(i));
                continue;
            }
            sb.append(maskStr);
        }
        return sb.toString();
    }

    /**
     * JSON字符串转List<Map>结构
     * 
     * @param data 原始数据Map
     * @param key 需要处理的JSON字段key
     */
    protected void processJsonData(Map<String, Object> data, String key) {
        LinkedHashMap<String, String> jsonMap =
            (LinkedHashMap<String, String>) data.get(key);

        List<Map<String, Object>> listMap = new ArrayList<>();
        ObjectMapper objectMapper = new ObjectMapper();

        if (jsonMap != null) {
            for (Map.Entry<String, String> entry : jsonMap.entrySet()) {
                String jsonStr = entry.getValue();

                Map<String, Object> taskMap = null;
                try {
                    taskMap = objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
                } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                }
                listMap.add(taskMap);

            }
        }
        data.put(key, listMap);
    }

    /**
     * 从包含 {{#xxx}} 标签的字符串中提取 xxx（集合名称）
     * 
     * @param input 包含模板标签的字符串
     * @return 提取到的集合名称，若格式不正确返回null
     */
    public static String extractCollectionName(String input) {

        if (input == null || input.isEmpty()) {
            return null;
        }
        int startTag = input.indexOf("{{#");
        int endTag = input.indexOf("}}", startTag);

        if (startTag == -1 || endTag == -1 || startTag >= endTag) {
            return null;
        }
        return input.substring(startTag + 3, endTag).trim();
    }
}
