package com.ruoyi.llm.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SnowFlake;
import com.ruoyi.llm.domain.CmcAgent;
import com.ruoyi.llm.domain.CmcChat;
import com.ruoyi.llm.domain.CmcDocument;
import com.ruoyi.llm.mapper.CmcAgentMapper;
import com.ruoyi.llm.mapper.CmcChatMapper;
import com.ruoyi.llm.mapper.CmcDocumentMapper;
import com.ruoyi.llm.service.ICmcAgentService;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.LoadCollectionReq;
import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.BaseVector;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.ChatResponse;
import org.noear.solon.ai.chat.ChatSession;
import org.noear.solon.ai.chat.message.ChatMessage;
import org.noear.solon.ai.chat.session.InMemoryChatSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 智能体Service业务层处理
 *
 * @author ruoyi
 * @date 2025-07-17
 */
@Service
public class CmcAgentServiceImpl implements ICmcAgentService
{
    @Autowired
    private CmcAgentMapper cmcAgentMapper;

    @Autowired
    private CmcDocumentMapper cmcDocumentMapper;

    @Autowired
    private CmcChatMapper cmcChatMapper;

    private String processValue = "";

    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();

    @Value("${cmc.llmService.url}")
    private String llmServiceUrl;

    @Value("${cmc.milvusService.url}")
    private String milvusServiceUrl;

    private MilvusClientV2 milvusClient;

    @PostConstruct
    public void initMilvusClient() {
        if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
            throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
        }
//        milvusClient = new MilvusClientV2(
//                ConnectConfig.builder()
//                        .uri(milvusServiceUrl)
//                        .build());
    }
    
    @PreDestroy
    public void destroyMilvusClient() {
        if (milvusClient != null) {
            try {
                // 关闭 Milvus 客户端，释放 gRPC 通道
                milvusClient.close();
                System.out.println("Milvus client closed successfully");
            } catch (Exception e) {
                System.err.println("Error closing Milvus client: " + e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 查询智能体
     *
     * @param agentId 智能体主键
     * @return 智能体
     */
    @Override
    public CmcAgent selectCmcAgentByAgentId(Integer agentId)
    {
        return cmcAgentMapper.selectCmcAgentByAgentId(agentId);
    }

    /**
     * 查询智能体列表
     *
     * @param cmcAgent 智能体
     * @return 智能体
     */
    @Override
    public List<CmcAgent> selectCmcAgentList(CmcAgent cmcAgent)
    {
        return cmcAgentMapper.selectCmcAgentList(cmcAgent);
    }

    /**
     * 获取开场白
     *
     * @param agentName 智能体名称
     * @return 结果
     */
    @Override
    public String getOpening(String agentName) {
        String content = "";
        if (agentName.contains("技术"))
            content = "我是投标文件写作助手，我将助您完成技术文件部分撰写。请上传招标询价文件，分析后将依招标服务要求，运用技术方案知识库，为您提供参考。";
        else if (agentName.contains("检查"))
            content = "我是文档检查助手，我将助您完成错别字检查。请上传文件。";
        return content;
    }

    /**
     * 上传单文件
     *
     * @param file 文件
     * @return 结果
     */
    @Override
    public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException {
        processValue = "";
        String prefixPath = "/upload/agent/" + agentName;
        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
        if (!profilePath.exists())
            profilePath.mkdirs();
        String chatId = new SnowFlake().generateId();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("chatId", chatId);
        File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
        if (!transferFile.exists())
            file.transferTo(transferFile);
        processValue = "上传完成:100%";
        CmcDocument cmcDocument = new CmcDocument();
        cmcDocument.setDocumentId(new SnowFlake().generateId());
        cmcDocument.setChatId(chatId);
        cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
        if (file.getOriginalFilename().endsWith(".pdf"))
            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
        cmcDocumentMapper.insertCmcDocument(cmcDocument);
        String message = "";
        CmcChat cmcChat = new CmcChat();
        cmcChat.setChatId(jsonObject.getString("chatId"));
        cmcChat.setInputTime(new Date());
        cmcChat.setInput("招标文件地址：" + prefixPath + "/" + file.getOriginalFilename());
        cmcChat.setUserId(SecurityUtils.getUserId());
        cmcChatMapper.insertCmcChat(cmcChat);
        if (agentName.contains("技术")) {
            String[] filenameSplit = file.getOriginalFilename().split("\\.");
            String outputFilename = prefixPath + "/" + file.getOriginalFilename()
                    .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
            if (file.getOriginalFilename().endsWith(".doc"))
                outputFilename = outputFilename.replace(".doc", ".docx");
            if (file.getOriginalFilename().endsWith(".pdf"))
                outputFilename = outputFilename.replace(".pdf", ".docx");
            Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
            Files.deleteIfExists(outputFilePath);
            InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
                // 保存文档到本地文件系统
                try (FileOutputStream out = new FileOutputStream(RuoYiConfig.getProfile() + outputFilename)) {
                    doc.write(out);
                }
            }
            String question = "工作大纲/工作范围/招标范围/服务范围";
            String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
                    RuoYiConfig.getProfile() + outputFilename, question);
            message = "好的，我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲：\n\n"+ chapters + "\n\n" +
                    "若您对章节标题有异议，请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改，后续将根据修改后的章节标题，帮您生成对应章节内容。\n\n" +
                    "思考时间可能较长，请耐心等待！\n";
        }
        else if (agentName.contains("检查")) {
            message = generateAnswerWithDocumentContent(profilePath + "/" + file.getOriginalFilename());
        }
        jsonObject.put("assistantMessage", message);
        return jsonObject;
    }

    /**
     * 上传修改文件
     *
     * @param file 文件
     * @return 结果
     */
    @Override
    public JSONObject uploadModifyFile(String topicId, MultipartFile file, String agentName) throws IOException {
        processValue = "";
        String prefixPath = "/upload/agent/" + agentName;
        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
        if (!profilePath.exists())
            profilePath.mkdirs();
        String chatId = new SnowFlake().generateId();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("chatId", chatId);
        File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
        if (!transferFile.exists())
            file.transferTo(transferFile);
        else {
            Path inputFilePath = Paths.get(profilePath + "/" + file.getOriginalFilename());
            Files.deleteIfExists(inputFilePath);
            file.transferTo(transferFile);
        }
        processValue = "文件上传完成:100%";
        CmcChat chat = new CmcChat();
        chat.setTopicId(topicId);
        String inputPath = RuoYiConfig.getProfile() + cmcChatMapper.selectCmcChatList(chat).get(0).getInput().split("：")[1];
        CmcDocument cmcDocument = new CmcDocument();
        cmcDocument.setDocumentId(new SnowFlake().generateId());
        cmcDocument.setChatId(chatId);
        cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
        if (file.getOriginalFilename().endsWith(".pdf"))
            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
        cmcDocumentMapper.insertCmcDocument(cmcDocument);
        String message = "";
        if (agentName.contains("技术")) {
            CmcChat cmcChat = new CmcChat();
            cmcChat.setChatId(jsonObject.getString("chatId"));
            cmcChat.setInputTime(new Date());
            cmcChat.setInput("投标文件地址：" + prefixPath + "/" + file.getOriginalFilename());
            cmcChat.setUserId(SecurityUtils.getUserId());
            cmcChatMapper.insertCmcChat(cmcChat);
            InputStream fileInputStream = new FileInputStream(profilePath + "/" + file.getOriginalFilename());
            String firstParagraphText = "";
            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
                firstParagraphText = doc.getParagraphs().get(1).getText();
                // 保存文档到本地文件系统
                try (FileOutputStream out = new FileOutputStream(profilePath + "/" + file.getOriginalFilename())) {
                    doc.write(out);
                }
            }
            List<String> subTitles = extractSubTitles(profilePath + "/" + file.getOriginalFilename(), "技术文件");
            List<String> firstParagraphSubTitles = extractSubTitles(profilePath + "/" + file.getOriginalFilename(), firstParagraphText);
            message = generateAnswerWithDocumentAndCollection(inputPath,profilePath + "/" + file.getOriginalFilename(), subTitles, firstParagraphSubTitles);
        }
        jsonObject.put("assistantMessage", message);
        return jsonObject;
    }

    /**
     * 上传多文件
     *
     * @param fileList 文件
     * @return 结果
     */
    @Override
    public String uploadDocumentList(MultipartFile[] fileList, String agentName) throws IOException {
        File profilePath = new File( RuoYiConfig.getProfile() + "/upload/agent/" + agentName);
        if (!profilePath.exists())
            profilePath.mkdirs();
        for (MultipartFile file : fileList) {
            File transferFile = new File(profilePath + File.separator + file.getOriginalFilename());
            if (!transferFile.exists())
                file.transferTo(transferFile);
        }
        return "上传成功";
    }

    /**
     * 上传多文件
     *
     * @return 结果
     */
    public String getProcess() {
        return processValue;
    }

    /**
     * 新增智能体
     *
     * @param cmcAgent 智能体
     * @return 结果
     */
    @Override
    public int insertCmcAgent(CmcAgent cmcAgent)
    {
        cmcAgent.setCreateTime(DateUtils.getNowDate());
        return cmcAgentMapper.insertCmcAgent(cmcAgent);
    }

    /**
     * 修改智能体
     *
     * @param cmcAgent 智能体
     * @return 结果
     */
    @Override
    public int updateCmcAgent(CmcAgent cmcAgent)
    {
        return cmcAgentMapper.updateCmcAgent(cmcAgent);
    }

    /**
     * 批量删除智能体
     *
     * @param agentIds 需要删除的智能体主键
     * @return 结果
     */
    @Override
    public int deleteCmcAgentByAgentIds(Integer[] agentIds)
    {
        return cmcAgentMapper.deleteCmcAgentByAgentIds(agentIds);
    }

    /**
     * 删除智能体信息
     *
     * @param agentId 智能体主键
     * @return 结果
     */
    @Override
    public int deleteCmcAgentByAgentId(Integer agentId)
    {
        return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
    }

    /**
     * 调用LLM生成回答 - 检查文档内容
     */
    public String generateAnswerWithDocumentContent(String documentPath) throws IOException {
        File profilePath = new File(documentPath);
        List<TextSegment> segments = splitDocument(profilePath, 10000, 0);
        StringBuilder sb = new StringBuilder();
        for (TextSegment segment : segments) {
            String prompt = "文档内容如下，帮我检查文档中用字、语言表达是否准确：\n" + segment.text();
            sb.append("片段").append(segments.indexOf(segment) + 1).append("：\n").append(generateAnswer(prompt));
            processValue = "检查中: " + Double.parseDouble(String.format("%.2f%n", (double) (segments.indexOf(segment) + 1)  / segments.size() * 100)) + "%";
        }
        sb.append("综合以上各片段检查内容修改建议，给出完整修改意见");
        return generateAnswer(sb.toString());
    }

    /**
     * 调用LLM生成回答 - 针对技术文件章节内容生成
     */
    public String generateAnswerWithDocumentAndCollection(String uploadFilePath, String templatePath, List<String> titles, List<String> firstParagraphSubTitles) throws IOException {
        File profilePath = new File(uploadFilePath);

        // 构建招标文件内容的嵌入向量存储
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
        embeddingStore.addAll(embeddings, segments);

        StringBuilder allGeneratedContent = new StringBuilder();
        Map<String, String> titleContentMap = new HashMap<>();

        List<String> successTitles = new ArrayList<>();
        List<String> failureTitles = new ArrayList<>();

        // 为每个章节标题生成内容
        for (String title : titles) {
            try {
                // 1. 从招标文件中检索相关内容
                List<String> tenderContent = new ArrayList<>();
                if (firstParagraphSubTitles.contains(title))
                    retrieveFromTenderDocument(embeddingStore, title, firstParagraphSubTitles);
                else
                    tenderContent.add(segments.get(0).text());
                // 2. 从知识库中检索相似章节内容
                List<JSONObject> knowledgeContexts = retrieveFromMilvus("technical", title, 10, "file_type != '标题'", firstParagraphSubTitles);

                // 3. 构建针对该章节的提示语
                String prompt = buildChapterPrompt(title, tenderContent, knowledgeContexts);

                // 4. 生成章节内容
                String chapterContent = generateAnswer(prompt);

                // 5. 存储章节内容
                titleContentMap.put(title, chapterContent);
                allGeneratedContent.append(title).append("\n").append(chapterContent).append("\n\n");

                // 更新进度
                processValue = "生成【" + title + "】章节内容中: " + Double.parseDouble(String.format("%.2f%n", (double) (titles.indexOf(title) + 1)  / titles.size() * 100)) + "%";
                successTitles.add(title);

            } catch (Exception e) {
                failureTitles.add(title);
                System.err.println("生成章节 " + title + " 内容时出错: " + e.getMessage());
                titleContentMap.put(title, "该章节内容生成失败，请手动填写。");
            }
        }

        // 将生成的内容写入技术文件
        writeContentToDocument(titleContentMap, titles, templatePath);

        // 构建返回消息
        StringBuilder message = new StringBuilder();
        message.append("技术文件章节内容生成完成！\n\n");
        message.append("已为以下章节生成内容：\n");
        for (String title : successTitles) {
            message.append("• ").append(title).append("\n");
        }
        message.append("以下章节内容待生成：\n");
        for (String title : failureTitles) {
            message.append("× ").append(title).append("\n");
        }
        message.append("\n技术文件已更新，请查看：\n");
        message.append("【<a href='/profile").append(templatePath.replace("\\", "/").replace(RuoYiConfig.getProfile(), ""))
               .append("'>点击查看技术文件</a>】\n\n");
        message.append("如需修改特定章节，请输入章节标题名称。\n").append("若提示未检测到工具，麻烦重新发送，谢谢");

        return message.toString();
    }

    /**
     * 从招标文件中检索与指定标题相关的内容
     */
    private List<String> retrieveFromTenderDocument(InMemoryEmbeddingStore<TextSegment> embeddingStore, String title, List<String> firstParagraphSubTitles) {
        List<String> relevantContent = new ArrayList<>();

        try {
            Embedding queryEmbedding = embeddingModel.embed(title).content();
            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                    .queryEmbedding(queryEmbedding)
                    .minScore(0.7) // 降低阈值以获取更多相关内容
                    .maxResults(5) // 限制结果数量
                    .build();

            List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
            results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());

            for (EmbeddingMatch<TextSegment> match : results) {
                relevantContent.add(match.embedded().text());
            }
        } catch (Exception e) {
            System.err.println("从招标文件检索内容时出错: " + e.getMessage());
        }
        return relevantContent;
    }

    /**
     * 构建针对特定章节的提示语
     */
    private String buildChapterPrompt(String title, List<String> tenderContent, List<JSONObject> knowledgeContexts) {
        StringBuilder prompt = new StringBuilder();

        // 基础指令
        prompt.append("你是一位专业的技术文件撰写专家。请根据以下信息为技术文件章节生成详细内容。\n\n");

        // 章节信息
        prompt.append("【目标章节】: ").append(title).append("\n\n");

        // 招标文件相关内容
        if (!tenderContent.isEmpty()) {
            prompt.append("【招标文件要求】:\n");
            for (int i = 0; i < tenderContent.size(); i++) {
                prompt.append(i + 1).append(". ").append(tenderContent.get(i)).append("\n");
            }
            prompt.append("\n");
        }

        // 知识库参考内容
        if (!knowledgeContexts.isEmpty()) {
            prompt.append("【参考模板内容】:\n");
            for (int i = 0; i < knowledgeContexts.size(); i++) {
                JSONObject context = knowledgeContexts.get(i);
                prompt.append("参考文件 ").append(i + 1).append(" (").append(context.getString("file_name")).append("):\n");
                prompt.append(context.getString("content")).append("\n\n");
            }
        }

        // 具体要求
        prompt.append("【撰写要求】:\n");
        prompt.append("1. 内容必须与招标文件要求高度匹配\n");
        prompt.append("2. 参考模板内容的结构和表达方式\n");
        prompt.append("3. 内容要专业、具体、可操作\n");
        prompt.append("4. 语言要规范、准确，符合技术文件标准\n");
        prompt.append("5. 内容要完整，包含必要的技术细节\n");
        prompt.append("6. 避免空洞的套话，要有实质性的技术内容\n\n");

        // 输出格式要求
        prompt.append("【输出格式】:\n");
        prompt.append("请直接输出章节的正文内容，不要包含标题。内容应该包括：\n");
        prompt.append("- 技术方案概述\n");
        prompt.append("- 具体实施方法\n");
        prompt.append("- 技术参数和标准\n");
        prompt.append("- 质量保证措施\n");
        prompt.append("- 其他相关技术细节\n\n");

        prompt.append("请开始生成 ").append(title).append(" 的详细内容：");

        return prompt.toString();
    }

    /**
     * 将生成的内容写入文档
     */
    private void writeContentToDocument(Map<String, String> titleContentMap, List<String> titles, String absolutePath) throws IOException {
        File file = new File(absolutePath);
        FileInputStream fileInputStream = new FileInputStream(file);

        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            List<XWPFParagraph> paragraphs = document.getParagraphs();
            List<Integer> titlePositions = new ArrayList<>();
            List<String> contents = new ArrayList<>();

            // 找到所有标题的位置
            for (int i = 0; i < paragraphs.size(); i++) {
                XWPFParagraph paragraph = paragraphs.get(i);
                String paragraphText = paragraph.getText().trim();

                for (String title : titles) {
                    if (paragraphText.equals(title)) {
                        titlePositions.add(i);
                        contents.add(titleContentMap.getOrDefault(title, ""));
                        break;
                    }
                }
            }

            // 从后往前插入内容，避免位置偏移
            for (int i = titlePositions.size() - 1; i >= 0; i--) {
                int insertPos = titlePositions.get(i) + 1;
                String content = contents.get(i);
                generateWordDocument(content, document, insertPos);
                // 更新进度
                processValue = "章节内容写入: " + Double.parseDouble(String.format("%.2f%n", (double) (i + 1)  / titles.size() * 100)) + "%";

            }

            // 保存文档
            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 调用LLM生成回答
     */
    public String generateAnswerWithDocument(String uploadFilePath, String templatePath, String question) throws IOException {
        StringBuilder sb = new StringBuilder("招标文件内容：\n\n");
        File profilePath = new File(uploadFilePath);
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
        embeddingStore.addAll(embeddings, segments);
        Embedding queryEmbedding = embeddingModel.embed(question).content();
        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
                .queryEmbedding(queryEmbedding)
                .minScore(0.7)
                .build();
        List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
        results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
        for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
            String requests = embeddingMatch.embedded().toString();
            sb.append(requests).append("\n\n");
        }
        //根据招标文件工作大纲要求生成二级标题
        sb.append("请基于上述招标文件中提到的")
                .append(question)
                .append("，先列出二级章节标题，严格按以下格式，仅输出标题列表：\n")
                .append("6.1 XX\n" +
                "6.2 XX\n" +
                "6.3 XX\n" +
                "......\n" +
                "6.n-1 XX\n" +
                "6.n XX\n");
        return writeChapters(sb.toString(), templatePath);
    }

    /**
     * 编排章节
     * @return
     */
    public String writeChapters(String prompt, String templatePath) throws IOException {
        String chapters2 = generateAnswer(prompt);

        //根据技术文档知识库生成二级标题下三级标题
        StringBuilder chapter3 = new StringBuilder();
        List<String> chapter2List = new ArrayList<>();
        String[] contentLines = chapters2.split("\n");
        for (String line : contentLines) {
            if (line.contains("6."))
                chapter2List.add(line.replace("*", "").replace("#", "").replace("-", ""));
        }
        processValue = "已生成二级标题:100%";
        for (String chapter2 : chapter2List) {
            String orderNum = chapter2.split(" ")[0];
            StringBuilder sb = new StringBuilder("二级标题如下：\n").append(chapter2)
                    .append("\n 请以上述二级标题作为检索词，到技术知识库中检索最相似的标题，结合检索结果编排该二级标题下的三级标题，严格按以下格式，仅输出三级标题列表：\n")
                    .append(orderNum).append(".1 XX\n")
                    .append(orderNum).append(".2 XX\n")
                    .append(orderNum).append(".3 XX\n");

            List<JSONObject> contexts = retrieveFromMilvus("technical", chapter2, 10, "file_type == '标题'", new ArrayList<>());
            for (JSONObject context : contexts) {
                sb.append("参考内容").append(": ")
                        .append(context.getString("content")).append("\n\n");
            }
            chapter3.append(generateAnswer(sb.toString()));
            processValue = "正在根据技术文档知识库生成二级标题下三级标题:" + Double.parseDouble(String.format("%.2f%n", (double) (chapter2List.indexOf(chapter2) + 1)  / chapter2List.size() * 100)) + "%";
        }
        String sb = "二级标题如下：\n" + chapters2 +
                "\n 三级标题如下：" + chapter3 +
                "请将上述二级与对应的三级标题进行合并，严格按以下格式输出完整大纲，仅输出标题：\n" +
                "6 技术文件\n" +
                "6.1 XX\n" +
                "6.1.1 XX\n" +
                "6.1.2 XX\n" +
                "6.2 XX\n" +
                "6.2.1 XX\n" +
                "6.2.2 XX\n" +
                "6.3 XX\n" +
                "6.3.1 XX\n" +
                "6.3.2 XX\n" +
                "...... \n" +
                "6.n XX\n" +
                "6.n.1 XX\n" +
                "6.n 2 XX\n";
        String content = generateAnswer(sb);
        writeTitles(content, templatePath);
        return content;
    }

    /**
     * 调用LLM生成回答
     * @return
     */
    public String generateAnswer(String prompt) throws IOException {
        ChatModel chatModel = ChatModel.of(llmServiceUrl)
                .model("Qwen")
                .build();

        List<ChatMessage> messages = new ArrayList<>();
        messages.add(ChatMessage.ofUser(prompt));
        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
        ChatResponse response = chatModel.prompt(chatSession).call();

        return response.lastChoice().getMessage().getContent();
    }

    /**
     * 从Milvus检索相关文档
     * @return
     */
    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK, String filter, List<String> firstParagraphSubTitles) {
        List<JSONObject> resultList = new ArrayList<>();
        if (firstParagraphSubTitles.isEmpty() || (!firstParagraphSubTitles.isEmpty() && !firstParagraphSubTitles.contains(query))) {
            List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK, filter);
            searchResultList.forEach(searchResult -> {
                JSONObject result = new JSONObject();
                result.put("content", searchResult.get(0).getEntity().get("content"));
                resultList.add(result);
            });
        }
        return resultList;
    }

    /**
     * 检索知识库
     */
    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK, String filter) {
        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));

        //  加载集合
        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
                .collectionName(collectionName)
                .build();
        milvusClient.loadCollection(loadCollectionReq);

        // 构建SearchParam
        Map<String, Object> searchParams = new HashMap<>();
        searchParams.put("nprobe", 8);
        SearchReq searchReq = SearchReq.builder()
                .collectionName(collectionName)
                .data(queryVector)
                .topK(topK)
                .outputFields(Arrays.asList("file_name", "file_type", "content"))
                .annsField("embedding")
                .metricType(IndexParam.MetricType.COSINE)
                .searchParams(searchParams)
                .build();
        
        if (filter != null && !filter.isEmpty()) {
            searchReq = SearchReq.builder()
                    .collectionName(collectionName)
                    .data(queryVector)
                    .topK(topK)
                    .filter(filter)
                    .outputFields(Arrays.asList("file_name", "file_type", "content"))
                    .annsField("embedding")
                    .metricType(IndexParam.MetricType.COSINE)
                    .searchParams(searchParams)
                    .build();
        }

        SearchResp searchResp = milvusClient.search(searchReq);
        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();

        // 释放集合
        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
                .collectionName(collectionName)
                .build();
        milvusClient.releaseCollection(releaseCollectionReq);

        return searchResultList;
    }

    /**
     * 写入章节大纲
     * @return
     */
    public void writeTitles(String content, String templatePath) throws IOException {
        List<String> chapters = new ArrayList<>();
        String[] contentLines = content.split("\n");
        for (String line : contentLines) {
            if (line.contains("6."))
                chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
        }
        File file = new File(templatePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            String otherTitles = "";
            for (int i = 0; i < chapters.size(); i++) {
                if (otherTitles.equals("") && chapters.get(i).split(" ")[1].startsWith("其它"))
                    otherTitles = chapters.get(i).split(" ")[0];
                if (!otherTitles.equals("") && chapters.get(i).split(" ")[0].startsWith(otherTitles) && !chapters.get(i).split(" ")[0].equals(otherTitles))
                    continue;
                XWPFParagraph contentParagraph = document.createParagraph();
                int dotNum = chapters.get(i).split("\\.").length - 1;
                contentParagraph.setStyle(String.valueOf(dotNum + 2));

                XWPFRun run = contentParagraph.createRun();
                if (chapters.get(i).split(" ").length > 1)
                    run.setText(chapters.get(i).split(" ")[1]);
                if (dotNum > 1)
                    run.addBreak();
                if (i < chapters.size() - 1 && chapters.get(i + 1).indexOf(".") == chapters.get(i + 1).lastIndexOf("."))
                    run.addBreak(BreakType.PAGE);
            }

            try (FileOutputStream out = new FileOutputStream(templatePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 写入章节内容
     * @return
     */
    public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
        String[] contentLines = content.split("\n");
        Map<String, String> map = new HashMap<>();
        File file = new File(absolutePath);
        FileInputStream fileInputStream = new FileInputStream(file);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            for (int i = 0; i < titles.size(); i++) {
                int startIndex = Arrays.asList(contentLines).indexOf(titles.get(i));
                StringBuilder text = new StringBuilder();
                if (startIndex >= 0) {
                    if (i < titles.size() - 1) {
                        int endIndex = Arrays.asList(contentLines).indexOf(titles.get(i + 1));
                        for (int c = startIndex + 1; c < endIndex; c++) {
                            text.append(contentLines[c]).append("\n\n");
                        }
                    } else {
                        if (startIndex + 1 < contentLines.length) {
                            for (int c = startIndex + 1; c < contentLines.length; c++) {
                                text.append(contentLines[c]).append("\n\n");
                            }
                        }
                    }
                }
                else
                    text.append(content);
                map.put(titles.get(i), text.toString());
            }

            List<Integer> positions = new ArrayList<>();
            List<String> contents = new ArrayList<>();
            List<XWPFParagraph> paragraphs = document.getParagraphs();

            for (int i = 0; i < paragraphs.size(); i++) {
                XWPFParagraph paragraph = paragraphs.get(i);
                for (String title : titles) {
                    if (paragraph.getText().contains(title)) {
                        positions.add(i);
                        contents.add(map.get(title));
                    }
                }
            }

            for (int i = positions.size() - 1; i >= 0; i--) {
                int insertPos = positions.get(i) + 1;
                XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
                XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
                contentParagraph.setStyle("1");
                XWPFRun run = contentParagraph.createRun();
                run.setText(contents.get(i));
            }

            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
                document.write(out);
            }
        }
    }

    /**
     * 获取最低级别子标题列表
     */
    public List<String> extractSubTitles(String filename, String question) throws IOException {
        List<String> subTitles = new ArrayList<>();
        InputStream fileInputStream = new FileInputStream(filename);

        boolean foundParent = false;
        int parentLevel = -1;
        // 用于跟踪当前路径
        List<XWPFParagraph> currentPath = new ArrayList<>();

        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {

            for (XWPFParagraph  paragraph : document.getParagraphs()) {
                String text = paragraph.getText();
                int level = Integer.parseInt(paragraph.getStyle());

                // 维护当前路径
                while (!currentPath.isEmpty() && Integer.parseInt(currentPath.get(currentPath.size() - 1).getStyle()) >= level) {
                    currentPath.remove(currentPath.size() - 1);
                }
                currentPath.add(paragraph);
                if (foundParent) {
                    if (level > parentLevel) { // 是子标题
                        if (isLeafNode(document, paragraph, level)) {
                            subTitles.add(text.replace("\n", ""));
                        }
                    } else {
                        // 遇到同级或更高级别的标题，停止搜索
                        break;
                    }
                } else if (text.equals(question)) {
                    foundParent = true;
                    parentLevel = level;
                }
            }


            processValue = "已获取最低级别子标题列表:100%";
            return subTitles;

        }
    }


    // 检查一个标题是否是叶子节点（没有更低级别的子标题）
    private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
        int index = doc.getPosOfParagraph(p);
        if (index == -1 || index >= doc.getParagraphs().size()-1) {
            return true;
        }

        // 检查后续段落
        XWPFParagraph nextP = doc.getParagraphs().get(index + 1);
        int nextLevel = Integer.parseInt(nextP.getStyle());
        // 遇到同级或更高级别标题，是叶子节点
        return nextLevel <= level; // 存在更低级别的标题，不是叶子节点

    }

    /**
     * 获取二、三级标题列表
     */
    public String extractTitles(String filename) throws IOException {
        StringBuilder subTitles = new StringBuilder();
        InputStream fileInputStream = new FileInputStream(filename);
        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                String text = paragraph.getText().trim();
                if (paragraph.getStyle() != null) {
                    // 判断主标题
                    if (paragraph.getStyle().equals("3") || paragraph.getStyle().equals("4") ) {
                        subTitles.append(text).append("\n");
                    }
                }
            }
        }
        return subTitles.toString();
    }

    /**
     * 分割文档
     */
    private List<TextSegment> splitDocument(File transferFile, int maxSegmentSizeInChars, int maxOverlapSizeInChars) throws IOException {
        // 加载文档
        InputStream fileInputStream = new FileInputStream(transferFile);
        String filename = transferFile.getName().toLowerCase();
        if (filename.endsWith(".docx")) {
            // 使用XWPFDocument处理DOCX文件，按二级标题分割（二级标题样式为2）
            List<TextSegment> segments = new ArrayList<>();
            try (XWPFDocument xwpfDocument = new XWPFDocument(fileInputStream)) {
                StringBuilder currentLevel2Content = new StringBuilder();
                boolean inLevel2Section = false;

                for (XWPFParagraph paragraph : xwpfDocument.getParagraphs()) {
                    String text = paragraph.getText().trim();
                    if (text.isEmpty()) continue;

                    if (paragraph.getStyle() != null) {
                        // 二级标题（样式2）开始新的分段
                        if (paragraph.getStyle().equals("3")) {
                            // 保存当前二级标题下的内容
                            if (currentLevel2Content.length() != 0) {
                                // 检查当前二级标题内容的字节数
                                int level2Length = currentLevel2Content.toString().getBytes().length;
                                if (level2Length <= 65535) {
                                    // 字数不大于65535，按二级标题分割
                                    TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                                    segments.add(segment);
                                } else {
                                    // 字数大于65535，按三级标题分割
                                    // 将当前二级标题内容按三级标题重新分割
                                    StringBuilder currentLevel3Content = new StringBuilder();
                                    String[] paragraphs = currentLevel2Content.toString().split("\n");
                                    for (String para : paragraphs) {
                                        if (para.trim().isEmpty()) continue;
                                        
                                        // 检查是否是三级标题
                                        boolean isLevel3Title = false;
                                        for (XWPFParagraph p : xwpfDocument.getParagraphs()) {
                                            if (p.getText().trim().equals(para.trim()) && p.getStyle() != null && p.getStyle().equals("4")) {
                                                isLevel3Title = true;
                                                break;
                                            }
                                        }
                                        
                                        if (isLevel3Title) {
                                            // 保存当前三级标题内容
                                            if (currentLevel3Content.length() != 0) {
                                                TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                                segments.add(segment);
                                                currentLevel3Content = new StringBuilder();
                                            }
                                            // 开始新的三级标题内容
                                            currentLevel3Content.append(para).append("\n");
                                        } else {
                                            // 普通内容，添加到当前三级标题
                                            if (currentLevel3Content.length() != 0) {
                                                currentLevel3Content.append(para).append("\n");
                                            }
                                        }
                                    }
                                    // 保存最后一个三级标题内容
                                    if (currentLevel3Content.length() != 0) {
                                        TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                        segments.add(segment);
                                    }
                                }
                                currentLevel2Content = new StringBuilder();
                            }
                            // 开始新的二级标题内容
                            currentLevel2Content.append(text).append("\n");
                            inLevel2Section = true;
                        }
                        // 其他标题样式（包括三级标题）
                        else {
                            if (inLevel2Section) {
                                currentLevel2Content.append(text).append("\n");
                            }
                        }
                    }
                    else {
                        // 普通内容段落
                        if (inLevel2Section) {
                            currentLevel2Content.append(text).append("\n");
                        }
                    }
                }

                // 保存最后一个二级标题内容
                if (currentLevel2Content.length() != 0) {
                    // 检查字节数
                    int level2Length = currentLevel2Content.toString().getBytes().length;
                    if (level2Length <= 65535) {
                        // 字数不大于65535，按二级标题分割
                        TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                        segments.add(segment);
                    } else {
                        // 字数大于65535，按三级标题分割
                        StringBuilder currentLevel3Content = new StringBuilder();
                        String[] paragraphs = currentLevel2Content.toString().split("\n");
                        for (String para : paragraphs) {
                            if (para.trim().isEmpty()) continue;
                            
                            boolean isLevel3Title = false;
                            for (XWPFParagraph p : xwpfDocument.getParagraphs()) {
                                if (p.getText().trim().equals(para.trim()) && p.getStyle() != null && p.getStyle().equals("4")) {
                                    isLevel3Title = true;
                                    break;
                                }
                            }
                            
                            if (isLevel3Title) {
                                // 保存当前三级标题内容
                                if (currentLevel3Content.length() != 0) {
                                    TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                    segments.add(segment);
                                    currentLevel3Content = new StringBuilder();
                                }
                                // 开始新的三级标题内容
                                currentLevel3Content.append(para).append("\n");
                            } else {
                                // 普通内容，添加到当前三级标题
                                if (currentLevel3Content.length() != 0) {
                                    currentLevel3Content.append(para).append("\n");
                                }
                            }
                        }
                        // 保存最后一个三级标题内容
                        if (currentLevel3Content.length() != 0) {
                            TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                            segments.add(segment);
                        }
                    }
                }
            }
            return segments;
        }
        else if (filename.endsWith(".doc")) {
            // 使用HWPFDocument处理DOC文件
            List<TextSegment> segments = new ArrayList<>();
            try (HWPFDocument hwpfDocument = new HWPFDocument(fileInputStream)) {
                StringBuilder currentLevel2Content = new StringBuilder();
                boolean inLevel2Section = false;

                Range range = hwpfDocument.getRange();
                for (int i = 0; i < range.numParagraphs(); i++) {
                    Paragraph paragraph = range.getParagraph(i);
                    String text = paragraph.text().trim();
                    if (text.isEmpty()) continue;

                    // 获取段落样式索引
                    short styleIndex = paragraph.getStyleIndex();

                    if (styleIndex == 2) {
                        // 二级标题，开始新的分段
                        // 保存当前二级标题下的内容
                        if (currentLevel2Content.length() != 0) {
                            // 检查当前二级标题内容的字节数
                            int level2Length = currentLevel2Content.toString().getBytes().length;
                            if (level2Length <= 65535) {
                                // 字数不大于65535，按二级标题分割
                                TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                                segments.add(segment);
                            } else {
                                // 字数大于65535，按三级标题分割
                                // 将当前二级标题内容按三级标题重新分割
                                StringBuilder currentLevel3Content = new StringBuilder();
                                Range level2Range = hwpfDocument.getRange();
                                boolean foundCurrentLevel2 = false;
                                
                                for (int j = 0; j < level2Range.numParagraphs(); j++) {
                                    Paragraph p = level2Range.getParagraph(j);
                                    String paraText = p.text().trim();
                                    if (paraText.isEmpty()) continue;
                                    
                                    // 找到当前二级标题的开始
                                    if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
                                        foundCurrentLevel2 = true;
                                    }
                                    
                                    if (foundCurrentLevel2) {
                                        short paraStyleIndex = p.getStyleIndex();
                                        
                                        if (paraStyleIndex == 3) {
                                            // 三级标题
                                            // 保存当前三级标题内容
                                            if (currentLevel3Content.length() != 0) {
                                                TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                                segments.add(segment);
                                                currentLevel3Content = new StringBuilder();
                                            }
                                            // 开始新的三级标题内容
                                            currentLevel3Content.append(paraText).append("\n");
                                        } else if (paraStyleIndex == 2 && !paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
                                            // 下一个二级标题，结束当前二级标题的处理
                                            break;
                                        } else {
                                            // 普通内容或当前二级标题
                                            if (currentLevel3Content.length() != 0 || paraStyleIndex == 2) {
                                                currentLevel3Content.append(paraText).append("\n");
                                            }
                                        }
                                    }
                                }
                                
                                // 保存最后一个三级标题内容
                                if (currentLevel3Content.length() != 0) {
                                    TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                    segments.add(segment);
                                }
                            }
                            currentLevel2Content = new StringBuilder();
                        }
                        // 开始新的二级标题内容
                        currentLevel2Content.append(text).append("\n");
                        inLevel2Section = true;
                    } else {
                        // 非二级标题或普通内容段落
                        if (inLevel2Section) {
                            currentLevel2Content.append(text).append("\n");
                        }
                    }
                }

                // 保存最后一个二级标题内容
                if (currentLevel2Content.length() != 0) {
                    // 检查字节数
                    int level2Length = currentLevel2Content.toString().getBytes().length;
                    if (level2Length <= 65535) {
                        // 字数不大于65535，按二级标题分割
                        TextSegment segment = TextSegment.from(currentLevel2Content.toString());
                        segments.add(segment);
                    } else {
                        // 字数大于65535，按三级标题分割
                        StringBuilder currentLevel3Content = new StringBuilder();
                        Range level2Range = hwpfDocument.getRange();
                        boolean foundCurrentLevel2 = false;
                        
                        for (int j = 0; j < level2Range.numParagraphs(); j++) {
                            Paragraph p = level2Range.getParagraph(j);
                            String paraText = p.text().trim();
                            if (paraText.isEmpty()) continue;
                            
                            // 找到当前二级标题的开始
                            if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
                                foundCurrentLevel2 = true;
                            }
                            
                            if (foundCurrentLevel2) {
                                short paraStyleIndex = p.getStyleIndex();
                                
                                if (paraStyleIndex == 3) {
                                    // 三级标题
                                    // 保存当前三级标题内容
                                    if (currentLevel3Content.length() != 0) {
                                        TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                                        segments.add(segment);
                                        currentLevel3Content = new StringBuilder();
                                    }
                                    // 开始新的三级标题内容
                                    currentLevel3Content.append(paraText).append("\n");
                                } else if (paraStyleIndex == 2 && !paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
                                    // 下一个二级标题，结束当前二级标题的处理
                                    break;
                                } else {
                                    // 普通内容或当前二级标题
                                    if (currentLevel3Content.length() != 0 || paraStyleIndex == 2) {
                                        currentLevel3Content.append(paraText).append("\n");
                                    }
                                }
                            }
                        }
                        
                        // 保存最后一个三级标题内容
                        if (currentLevel3Content.length() != 0) {
                            TextSegment segment = TextSegment.from(currentLevel3Content.toString());
                            segments.add(segment);
                        }
                    }
                }
            }
            return segments;
        }

        else if (filename.endsWith(".pdf")) {
            Document document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(maxSegmentSizeInChars, maxOverlapSizeInChars);
            return splitter.split(document);
        }
        else if (filename.endsWith(".txt")) {
            Document document = new TextDocumentParser().parse(fileInputStream);
            DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(maxSegmentSizeInChars, maxOverlapSizeInChars);
            return splitter.split(document);
        }
        else {
            throw new UnsupportedOperationException("不支持文件类型: " + filename);
        }
    }

    public void generateWordDocument(String content, XWPFDocument document, int insertPos) {
        try {
            // 规范化锚点：在 insertPos 对应段落之前插入；若越界则附加到末尾
            List<XWPFParagraph> paragraphs = document.getParagraphs();
            if (paragraphs.isEmpty()) {
                document.createParagraph();
                paragraphs = document.getParagraphs();
            }
            if (insertPos < 0) {
                insertPos = 0;
            }
            if (insertPos >= paragraphs.size()) {
                // 在文末追加一个空段落作为锚点
                XWPFParagraph tail = document.createParagraph();
                paragraphs = document.getParagraphs();
                insertPos = paragraphs.indexOf(tail);
            }

            String[] lines = content.split("\n");
            boolean inTable = false;
            List<String> tableLines = new ArrayList<>();

            // 将内容解析为块（普通段落/表格/标题/列表项等），然后倒序插入，确保顺序正确
            class Block {
                String type; // para | table | h4 | list1 | list2 | imageText | boldOnly | numberedBold
                String text;
                List<String> table;
                Integer h4Index;       // 预计算的四级标题编号
                Integer list1Index;    // 预计算的一级列表编号 1),2),3)
                Integer list2Index;    // 预计算的二级列表编号 (1),(2),(3)
                String boldText;      // 加粗文本部分
                String suffixText;    // 后缀文本（如冒号及后续内容）
                String numberPrefix;  // 数字前缀，如 "1. "
                Block(String t, String x) { type = t; text = x; }
                Block(List<String> tbl) { type = "table"; table = new ArrayList<>(tbl); }
                Block(String t, String x, Integer h4Idx, Integer l1Idx, Integer l2Idx) {
                    type = t; text = x; h4Index = h4Idx; list1Index = l1Idx; list2Index = l2Idx;
                }
                Block(String t, String bold, String suffix) {
                    type = t; boldText = bold; suffixText = suffix;
                }
                Block(String t, String numPrefix, String bold, String suffix) {
                    type = t; numberPrefix = numPrefix; boldText = bold; suffixText = suffix;
                }
            }
            List<Block> blocks = new ArrayList<>();

            // 预计算编号（按正序扫描）
            int h4Counter = 0;
            int list1Counter = 0;
            int list2Counter = 0;
            String previousLine = ""; // 记录上一行内容

            for (String raw : lines) {
                String line = raw.trim();
                if (line.isEmpty()) {
                    blocks.add(new Block("para", "")); // 空段落
                    previousLine = line;
                    continue;
                }

                // 检查上一行是否以中文冒号结尾，如果是则重置列表编号
                if (previousLine.endsWith("：") || previousLine.endsWith(":")) {
                    list1Counter = 0;
                    list2Counter = 0;
                }

                if (line.startsWith("#")) {
                    // 跳过前三级标题
                    previousLine = line;
                    continue;
                } else if (line.startsWith("####")) {
                    // 四级标题，编号采用预计算，保证倒序插入后视觉顺序仍递增
                    String title = line.replace("####", "").trim();
                    title = title.replaceFirst("^\\d+\\.\\s*", "");
                    h4Counter++;
                    // 碰到新小节，重置列表编号
                    list1Counter = 0;
                    list2Counter = 0;
                    blocks.add(new Block("h4", title, h4Counter, null, null));
                } else if (line.startsWith("|")) {
                    // 表格模式开始
                    if (!inTable) {
                        inTable = true;
                        tableLines.clear();
                    }
                    tableLines.add(line);
                } else if (inTable && !line.startsWith("|")) {
                    // 表格结束，收集为一个块
                    inTable = false;
                    if (!tableLines.isEmpty()) {
                        blocks.add(new Block(new ArrayList<>(tableLines)));
                        tableLines.clear();
                    }
                    // 当前行按普通段落处理
                    blocks.add(new Block("para", line));
                } else if (inTable) {
                    tableLines.add(line);
                } else if (line.startsWith("- **")) {
                    // 一级加粗列表项，编号为 1),2),3) —— 预计算编号
                    String listItem = line.replaceFirst("- \\*\\*", "").replace("**", "");
                    listItem = listItem.replaceFirst("^\\*\\*", "").replace("**：", "：");
                    list1Counter++;
                    // 一级列表开始时，重置二级列表编号
                    list2Counter = 0;
                    Block b = new Block("list1", listItem, null, list1Counter, null);
                    blocks.add(b);
                } else if (line.startsWith("  - **")) {
                    // 二级加粗列表项，编号为 (1),(2),(3) —— 预计算编号
                    String listItem = line.replaceFirst("  - \\*\\*", "").replace("**", "");
                    listItem = listItem.replaceFirst("^\\*\\*", "").replace("**：", "：");
                    list2Counter++;
                    Block b = new Block("list2", listItem, null, null, list2Counter);
                    blocks.add(b);
                } else {
                    // 匹配格式：1. **文本**（数字前缀+加粗）
                    Pattern numberedBoldPattern = Pattern.compile("^(\\d+\\.\\s+)\\*\\*([^*]+)\\*\\*(.*)$");
                    java.util.regex.Matcher nbm = numberedBoldPattern.matcher(line);
                    if (nbm.matches()) {
                        String numPrefix = nbm.group(1);
                        String bold = nbm.group(2);
                        String suf = nbm.group(3);
                        if (suf == null) suf = "";
                        blocks.add(new Block("numberedBold", numPrefix, bold, suf));
                        previousLine = line;
                        continue;
                    }
                    // 匹配格式：**文本**
                    Pattern boldOnlyPattern = Pattern.compile("^\\*\\*([^*]+)\\*\\*(.*)$");
                    java.util.regex.Matcher boldMatcher = boldOnlyPattern.matcher(line);
                    if (boldMatcher.matches()) {
                        String boldText = boldMatcher.group(1);      // "数据校验"
                        String suffix = boldMatcher.group(2);        // "："或后续文本，可能为空
                        if (suffix == null) suffix = "";
                        blocks.add(new Block("boldOnly", boldText, suffix));
                        previousLine = line;
                        continue;
                    }
                    // 如果不是 boldOnly 格式，继续后续处理
                    if (line.startsWith("![") && line.contains("](") && line.contains(")")) {
                        // 图片 markdown，转成说明文本
                        String imageText = extractImageDescription(line);
                        blocks.add(new Block("imageText", imageText));
                    } else if (line.contains("http") && (line.contains(".png") || line.contains(".jpg") || line.contains(".jpeg") || line.contains(".gif"))) {
                        blocks.add(new Block("imageText", "图片链接: " + line));
                    } else {
                        blocks.add(new Block("para", line));
                    }
                }
                
                // 更新上一行记录
                previousLine = line;
            }
            // 收尾：如果文件最后是表格仍未输出
            if (inTable && !tableLines.isEmpty()) {
                blocks.add(new Block(new ArrayList<>(tableLines)));
                tableLines.clear();
            }

            // 倒序插入到 insertPos 前（即插在 insertPos 之前，从后往前插保证最终顺序）
            for (int i = blocks.size() - 1; i >= 0; i--) {
                Block b = blocks.get(i);
                // 每次获取最新段落列表和锚点，避免索引失效
                paragraphs = document.getParagraphs();
                if (insertPos >= paragraphs.size()) {
                    XWPFParagraph tail = document.createParagraph();
                    paragraphs = document.getParagraphs();
                    insertPos = paragraphs.indexOf(tail);
                }
                XmlCursor cursor = paragraphs.get(insertPos).getCTP().newCursor();

                switch (b.type) {
                    case "h4": {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        XWPFRun run = p.createRun();
                        int num = (b.h4Index != null ? b.h4Index : 1);
                        run.setText("6.1.1." + num + " " + b.text);
                        run.setBold(true);
                        run.setFontSize(14);
                        break;
                    }
                    case "list1": {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        XWPFRun run = p.createRun();
                        int num = (b.list1Index != null ? b.list1Index : 1);
                        run.setText(num + ") " + b.text);
                        run.setFontSize(12);
                        break;
                    }
                    case "list2": {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        XWPFRun run = p.createRun();
                        int num = (b.list2Index != null ? b.list2Index : 1);
                        run.setText("   (" + num + ") " + b.text);
                        run.setFontSize(12);
                        break;
                    }
                    case "numberedBold": {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        // 数字前缀（不加粗）
                        if (b.numberPrefix != null) {
                            XWPFRun r1 = p.createRun();
                            r1.setText(b.numberPrefix);
                            r1.setFontSize(12);
                        }
                        // 加粗文本
                        XWPFRun r2 = p.createRun();
                        r2.setText(b.boldText != null ? b.boldText : "");
                        r2.setFontSize(12);
                        r2.setBold(true);
                        // 后缀（不加粗）
                        if (b.suffixText != null && !b.suffixText.isEmpty()) {
                            XWPFRun r3 = p.createRun();
                            r3.setText(b.suffixText);
                            r3.setFontSize(12);
                        }
                        break;
                    }
                    case "boldOnly": {
                        // 处理格式：**文本** 或 **文本**： 转换为 Word 中的部分加粗
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        
                        // 加粗文本部分
                        XWPFRun run1 = p.createRun();
                        run1.setText(b.boldText);
                        run1.setFontSize(12);
                        run1.setBold(true);
                        
                        // 后缀部分（不加粗，如冒号及后续内容，如果有的话）
                        if (b.suffixText != null && !b.suffixText.isEmpty()) {
                            XWPFRun run2 = p.createRun();
                            run2.setText(b.suffixText);
                            run2.setFontSize(12);
                        }
                        break;
                    }
                    case "imageText": {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        XWPFRun run = p.createRun();
                        run.setText(b.text);
                        run.setFontSize(12);
                        run.setItalic(true);
                        run.setColor("808080");
                        break;
                    }
                    case "table": {
                        // 解析 markdown 表格并插入在 cursor 位置
                        List<String[]> tableData = new ArrayList<>();
                        Pattern sep = Pattern.compile("^\\|\\s*(-+\\s*\\|\\s*)*-+\\s*\\|?$");
                        for (String tl : b.table) {
                            if (sep.matcher(tl).matches()) continue;
                            String t = tl.trim();
                            if (t.startsWith("|")) t = t.substring(1);
                            if (t.endsWith("|")) t = t.substring(0, t.length() - 1);
                            String[] cells = t.split("\\|", -1);
                            for (int c = 0; c < cells.length; c++) cells[c] = cells[c].trim();
                            tableData.add(cells);
                        }
                        if (!tableData.isEmpty()) {
                            XWPFTable table = document.insertNewTbl(cursor);
                            table.setStyleID("10");
                            // 初始化行列
                            int rows = tableData.size();
                            int cols = tableData.get(0).length;
                            while (table.getNumberOfRows() < rows) table.createRow();
                            for (int r = 0; r < rows; r++) {
                                XWPFTableRow tr = table.getRow(r);
                                // 首行已存在的列数可能不足，补齐
                                while (tr.getTableCells().size() < cols) tr.addNewTableCell();
                                String[] rowData = tableData.get(r);
                                for (int c = 0; c < cols; c++) {
                                    XWPFTableCell cell = tr.getCell(c);
                                    cell.removeParagraph(0);
                                    XWPFParagraph cp = cell.addParagraph();
                                    XWPFRun cr = cp.createRun();
                                    cr.setText(rowData[c]);
                                    if (r == 0) {
                                        cr.setBold(true);
                                        cp.setAlignment(ParagraphAlignment.CENTER);
                                    } else {
                                        cp.setAlignment(ParagraphAlignment.LEFT);
                                    }
                                }
                            }
                            table.setWidth("100%");
                        }
                        break;
                    }
                    case "para":
                    default: {
                        XWPFParagraph p = document.insertNewParagraph(cursor);
                        XWPFRun run = p.createRun();
                        if (b.text != null) {
                            run.setText(b.text);
                        }
                        run.setFontSize(12);
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从图片Markdown格式中提取描述文本
     */
    private String extractImageDescription(String imageLine) {
        // 格式: ![描述](链接)
        if (imageLine.startsWith("![") && imageLine.contains("](") && imageLine.contains(")")) {
            int descStart = imageLine.indexOf('[') + 1;
            int descEnd = imageLine.indexOf(']');
            int urlStart = imageLine.indexOf('(') + 1;
            int urlEnd = imageLine.indexOf(')');

            if (descStart > 0 && descEnd > descStart && urlStart > descEnd && urlEnd > urlStart) {
                String description = imageLine.substring(descStart, descEnd);
                String url = imageLine.substring(urlStart, urlEnd);
                return "图: " + description + " (" + url + ")";
            }
        }
        return imageLine; // 如果格式不正确，返回原文本
    }

}


