package com.njcn.gather.report.util;

import com.njcn.gather.report.pojo.constant.ItemAnchorConstant;
import com.njcn.gather.tools.report.util.BookmarkUtil;
import com.njcn.gather.tools.report.util.Docx4jUtil;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.CTBookmark;
import org.docx4j.wml.CTMarkupRange;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.Text;

import javax.xml.bind.JAXBElement;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * "指标锚点"模板的区段隐藏 / 编号重排 / 书签清理工具。
 * 只服务新路径 dealDataModelByItemAnchor，不被老路径引用。
 * 设计文档：docs/superpowers/specs/2026-07-02-item-anchor-report-template-design.md §4.2
 *
 * @author hongawen
 */
@Slf4j
public final class ItemAnchorSectionUtil {

    private ItemAnchorSectionUtil() {
    }

    /** 三级编号：7.x.y（半角点） */
    private static final Pattern LEVEL3 = Pattern.compile("^7\\.(\\d+)\\.(\\d+)");
    /** 二级编号：7.x（半角点，排除 7.x.y） */
    private static final Pattern LEVEL2 = Pattern.compile("^7\\.(\\d+)(?![\\.\\d])");

    /**
     * 删除已填充 item_* 的空锚点段，避免插入表格前残留一行空段落。
     * 只处理指标锚点新路径的白名单 item_*；段落内存在正文时保留，防止模板内容被误删。
     */
    public static boolean removeEmptyItemAnchorParagraph(BookmarkUtil.BookmarkInfo info) {
        if (info == null || info.bookmark == null || info.parentParagraph == null || info.parentContainer == null) {
            return false;
        }
        if (ItemAnchorConstant.resolveItemScriptCode(info.bookmark.getName()) == null) {
            return false;
        }
        String text = Docx4jUtil.getTextFromP(info.parentParagraph);
        if (text != null && !text.trim().isEmpty()) {
            return false;
        }
        return info.parentContainer.getContent().remove(info.parentParagraph);
    }

    /**
     * 删除隐藏指标对应的 sect_{code} 区段（body 块级整段删除）。
     * GRP 折叠规则：GRP_CHILDREN 中某组的子节全部在 hiddenCodes 中时，删父节区段（含组标题与组内公共文字），
     * 不再单删子节；父区段定位失败时回退按子节各自删除，避免组和子节一个都删不掉（静默违反红线 2）。
     */
    public static void removeSectRanges(MainDocumentPart part, Set<String> hiddenCodes) {
        if (part == null || hiddenCodes == null || hiddenCodes.isEmpty()) {
            return;
        }
        List<Object> body = part.getContent();
        // 单遍扫描收集全部书签区段，后续按名 O(1) 取用
        Map<String, int[]> allRanges = locateRanges(body);
        List<int[]> ranges = new ArrayList<>();
        Set<String> coveredByGrp = new HashSet<>();
        for (Map.Entry<String, List<String>> entry : ItemAnchorConstant.GRP_CHILDREN.entrySet()) {
            if (!hiddenCodes.containsAll(entry.getValue())) {
                continue;
            }
            int[] grpRange = allRanges.get((ItemAnchorConstant.SECT_PREFIX + entry.getKey()).toLowerCase());
            if (grpRange != null) {
                ranges.add(grpRange);
                coveredByGrp.addAll(entry.getValue());
            } else {
                log.warn("父区段书签 {}{} 定位失败，回退按子节区段各自删除，请检查模板书签是否完整",
                        ItemAnchorConstant.SECT_PREFIX, entry.getKey());
            }
        }
        for (String code : hiddenCodes) {
            if (coveredByGrp.contains(code)) {
                continue;
            }
            int[] range = allRanges.get((ItemAnchorConstant.SECT_PREFIX + code).toLowerCase());
            if (range != null) {
                ranges.add(range);
            } else {
                log.warn("区段书签 {}{} 定位失败，该节无法隐藏，请检查模板书签是否完整",
                        ItemAnchorConstant.SECT_PREFIX, code);
            }
        }
        // 起点降序删除，避免前面的删除让后面的索引失效
        ranges.sort((a, b) -> Integer.compare(b[0], a[0]));
        for (int[] range : ranges) {
            for (int i = range[1]; i >= range[0]; i--) {
                if (i < body.size()) {
                    body.remove(i);
                }
            }
        }
    }

    /**
     * 清理全文残留的 item_ / sect_ 书签（start 与配对的 end），其他书签不动。
     * 已填充节的 sect end 会被 BookmarkUtil.removeBookmark 顺带清掉，留下无 end 的 start，本方法兜底清理。
     */
    public static void stripItemAnchorBookmarks(MainDocumentPart part) {
        if (part == null) {
            return;
        }
        Set<BigInteger> ourIds = new HashSet<>();
        // 第一遍：删 start 并记 id
        for (Object block : part.getContent()) {
            P p = asP(block);
            if (p == null) {
                continue;
            }
            Iterator<Object> it = p.getContent().iterator();
            while (it.hasNext()) {
                Object v = unwrap(it.next());
                if (v instanceof CTBookmark) {
                    String name = ((CTBookmark) v).getName();
                    if (isOurNamespace(name)) {
                        ourIds.add(((CTBookmark) v).getId());
                        it.remove();
                    }
                }
            }
        }
        // 第二遍：删配对 end
        for (Object block : part.getContent()) {
            P p = asP(block);
            if (p == null) {
                continue;
            }
            Iterator<Object> it = p.getContent().iterator();
            while (it.hasNext()) {
                Object v = unwrap(it.next());
                if (v instanceof CTMarkupRange && !(v instanceof CTBookmark)
                        && ourIds.contains(((CTMarkupRange) v).getId())) {
                    it.remove();
                }
            }
        }
    }

    /**
     * 第 7 节编号重排：区段删除后，把剩余的 7.x / 7.x.y 标题按出现顺序重写为连续编号。
     * 顶级"7．准确度检查："用全角点，正则天然不匹配，保持不动。
     */
    public static void renumberChapter7(MainDocumentPart part) {
        if (part == null) {
            return;
        }
        int l2 = 0;
        int l3 = 0;
        for (Object block : part.getContent()) {
            P p = asP(block);
            if (p == null) {
                continue;
            }
            String text = Docx4jUtil.getTextFromP(p);
            if (text == null) {
                continue;
            }
            Matcher m3 = LEVEL3.matcher(text);
            if (m3.find()) {
                l3++;
                String newPrefix = "7." + l2 + "." + l3;
                if (!m3.group().equals(newPrefix)) {
                    replacePrefix(p, m3.group().length(), newPrefix);
                }
                continue;
            }
            Matcher m2 = LEVEL2.matcher(text);
            if (m2.find()) {
                l2++;
                l3 = 0;
                String newPrefix = "7." + l2;
                if (!m2.group().equals(newPrefix)) {
                    replacePrefix(p, m2.group().length(), newPrefix);
                }
            }
        }
    }

    /**
     * 单遍扫描 body，收集全部段落内书签区段：书签名（小写）→ [start块下标, end块下标]。
     * 同名书签只记首个（与逐名定位"取第一处命中"语义一致）；无配对 end 的 start 不产出条目。
     */
    private static Map<String, int[]> locateRanges(List<Object> body) {
        Map<String, int[]> out = new HashMap<>();
        Map<BigInteger, String> openNames = new HashMap<>();
        Map<BigInteger, Integer> openStarts = new HashMap<>();
        for (int i = 0; i < body.size(); i++) {
            P p = asP(body.get(i));
            if (p == null) {
                continue;
            }
            for (Object o : p.getContent()) {
                Object v = unwrap(o);
                // CTBookmark 继承 CTMarkupRange：先判 start，else 分支天然排除 CTBookmark，即 bookmarkEnd
                if (v instanceof CTBookmark) {
                    CTBookmark start = (CTBookmark) v;
                    if (start.getName() != null && start.getId() != null) {
                        openNames.put(start.getId(), start.getName().toLowerCase());
                        openStarts.put(start.getId(), i);
                    }
                } else if (v instanceof CTMarkupRange) {
                    BigInteger id = ((CTMarkupRange) v).getId();
                    String name = openNames.remove(id);
                    if (name != null) {
                        out.putIfAbsent(name, new int[]{openStarts.remove(id), i});
                    }
                }
            }
        }
        return out;
    }

    private static boolean isOurNamespace(String name) {
        if (name == null) {
            return false;
        }
        String lower = name.toLowerCase();
        return lower.startsWith(ItemAnchorConstant.ITEM_PREFIX) || lower.startsWith(ItemAnchorConstant.SECT_PREFIX);
    }

    private static Object unwrap(Object o) {
        return (o instanceof JAXBElement) ? ((JAXBElement<?>) o).getValue() : o;
    }

    private static P asP(Object block) {
        Object v = unwrap(block);
        return (v instanceof P) ? (P) v : null;
    }

    /** 跨 run 替换段首前 oldLen 个字符为 newPrefix（编号可能被 Word 拆进多个 text 节点） */
    private static void replacePrefix(P p, int oldLen, String newPrefix) {
        List<Text> texts = new ArrayList<>();
        collectTexts(p.getContent(), texts);
        int remaining = oldLen;
        boolean inserted = false;
        for (Text t : texts) {
            if (remaining <= 0) {
                break;
            }
            String value = t.getValue() == null ? "" : t.getValue();
            int cut = Math.min(remaining, value.length());
            remaining -= cut;
            String rest = value.substring(cut);
            t.setValue(inserted ? rest : newPrefix + rest);
            inserted = true;
        }
    }

    private static void collectTexts(List<Object> content, List<Text> out) {
        for (Object o : content) {
            Object v = unwrap(o);
            if (v instanceof R) {
                collectTexts(((R) v).getContent(), out);
            } else if (v instanceof Text) {
                out.add((Text) v);
            }
        }
    }
}
