package com.axis.innovators.box.decompilation.gui;

import com.axis.innovators.box.gui.LoadIcon;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.Position;
import com.github.javaparser.ast.CompilationUnit;
import org.apache.commons.compress.utils.IOUtils;
import org.benf.cfr.reader.api.CfrDriver;
import org.fife.ui.rsyntaxtextarea.*;
import org.fife.ui.rtextarea.RTextScrollPane;
import com.github.javaparser.ast.expr.MethodCallExpr;

import com.github.javaparser.ast.body.MethodDeclaration;

import java.awt.event.*;
import java.util.concurrent.ConcurrentHashMap;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author tzdwindows 7
 */
public class ModernJarViewer extends JFrame {
    private final DefaultListModel<SearchResult> globalSearchResults = new DefaultListModel<>();
    private JTree fileTree;
    private JTabbedPane contentPane;
    private RSyntaxTextArea codeArea;
    private JLabel imageLabel;
    private File currentJarFile;
    private final Map<String, MethodInfo> methodIndex = new ConcurrentHashMap<>();
    private MethodLink currentHoveredMethod;
    private JavaParser javaParser = new JavaParser();
    private DefaultMutableTreeNode root;

    public ModernJarViewer(Frame owner) {
        setTitle("Jar反编译工具");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        initComponents();
    }

    /****/
    public ModernJarViewer(Frame owner, String jarFile) {
        setTitle("Jar反编译工具");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        initComponents();
        loadJar(new File(jarFile));
    }

    private void initComponents() {
        setSize(1280, 800);
        setLayout(new BorderLayout());

        setLocationRelativeTo(null);

        setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage());

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("文件");
        JMenuItem openItem = new JMenuItem("打开");
        openItem.addActionListener(e -> openJarFile());
        fileMenu.add(openItem);
        menuBar.add(fileMenu);
        setJMenuBar(menuBar);

        root = new DefaultMutableTreeNode("根目录");
        fileTree = new JTree(new DefaultTreeModel(root));
        fileTree.addTreeSelectionListener(new TreeSelectionHandler());
        JScrollPane treeScroll = new JScrollPane(fileTree);

        codeArea = new RSyntaxTextArea();
        codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
        codeArea.setCodeFoldingEnabled(true);
        codeArea.setAntiAliasingEnabled(true);
        codeArea.setFont(new Font("Consolas", Font.PLAIN, 14));
        codeArea.setEditable(false);

        JPopupMenu popupMenu = createRightClickMenu();
        codeArea.setComponentPopupMenu(popupMenu);

        codeArea.setHyperlinksEnabled(true);
        codeArea.addHyperlinkListener(e -> {
            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                handleHyperlinkClick(e.getURL());
            }
        });

        codeArea.setToolTipSupplier((textArea, mouseEvent) -> {
            currentHoveredMethod = findMethodUnderCursor(mouseEvent.getPoint());
            if (currentHoveredMethod != null) {
                return buildMethodTooltip(currentHoveredMethod);
            }

            String javadoc = getJavadocAtCursor();
            return javadoc != null ? formatJavadoc(javadoc) : null;
        });

        codeArea.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                handleMethodClick(e);
            }
        });

        JTextPane popupTextPane = new JTextPane();
        popupTextPane.setContentType("text/html");
        popupTextPane.setEditable(false);
        ToolTipManager.sharedInstance().registerComponent(codeArea);
        codeArea.setToolTipSupplier((textArea, mouseEvent) -> {
            String javadoc = getJavadocAtCursor();
            if (javadoc != null) {
                popupTextPane.setText(formatJavadoc(javadoc));
                return String.valueOf(popupTextPane);
            }
            return null;
        });

        codeArea.setBackground(new Color(0x1E1F22));
        //codeArea.setForeground(new Color(0xBBBBBB));
        codeArea.setSelectionColor(new Color(0x214283));
        codeArea.setCurrentLineHighlightColor(new Color(0x323232));


        RTextScrollPane codeScrollPane = new RTextScrollPane(codeArea);
        codeScrollPane.setLineNumbersEnabled(true);
        codeScrollPane.setFoldIndicatorEnabled(true);

        imageLabel = new JLabel();
        JScrollPane imageScrollPane = new JScrollPane(imageLabel);

        contentPane = new JTabbedPane();
        contentPane.addTab("代码", codeScrollPane);
        contentPane.addTab("图片", imageScrollPane);
        codeArea.setCodeFoldingEnabled(true);

        Font ideaFont = new Font("JetBrains Mono", Font.PLAIN, 13);
        if (!Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()).contains("JetBrains Mono")) {
            ideaFont = new Font("Consolas", Font.PLAIN, 14);
        }
        codeArea.setFont(ideaFont);

        SyntaxScheme scheme = new SyntaxScheme(true);
        scheme = configureIDEATheme(scheme);
        codeArea.setSyntaxScheme(scheme);

        JSplitPane splitPane = new JSplitPane(
                JSplitPane.HORIZONTAL_SPLIT,
                treeScroll,
                contentPane
        );
        splitPane.setDividerLocation(300);
        add(splitPane, BorderLayout.CENTER);
        setupKeyBindings();
        JList<SearchResult> globalSearchList = new JList<>(globalSearchResults);
        globalSearchList.setCellRenderer(new SearchResultRenderer());
    }

    private JPopupMenu createRightClickMenu() {
        JPopupMenu popupMenu = new JPopupMenu();

        JMenuItem gotoMethodItem = new JMenuItem("跳转到方法定义");
        gotoMethodItem.addActionListener(e -> {
            Point mousePosition = codeArea.getMousePosition();
            if (mousePosition != null) {
                MethodLink link = findMethodUnderCursor(mousePosition);
                if (link != null) {
                    navigateToMethod(link);
                } else {
                    JOptionPane.showMessageDialog(this, "未找到方法定义", "提示", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        });
        popupMenu.add(gotoMethodItem);

        return popupMenu;
    }

    private String decompileClass(JarEntry entry) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            CFROutputSinkFactory sinkFactory = new CFROutputSinkFactory();
            CfrDriver driver = new CfrDriver.Builder()
                    .withClassFileSource(new JarClassFileSource(jar))
                    .withOutputSink(sinkFactory)
                    .build();

            driver.analyse(Collections.singletonList(entry.getName()));
            return sinkFactory.getOutput();
        } catch (Exception e) {
            e.printStackTrace();
            return "// Decompilation failed\n";
        }
    }

    private static class SearchResultRenderer extends DefaultListCellRenderer {
        public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                                                      boolean isSelected, boolean cellHasFocus) {
            SearchResult result = (SearchResult) value;
            JLabel label = (JLabel) super.getListCellRendererComponent(
                    list, value, index, isSelected, cellHasFocus);
            label.setText("<html>" + result.filePath + ": <b>" + result.matchText + "</b></html>");
            return label;
        }
    }

    private JDialog currentSearchDialog = null;
    private JDialog currentGlobalSearchDialog = null;
    private void setupKeyBindings() {
        // 本地文件搜索
        KeyStroke ctrlF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK);
        codeArea.getInputMap().put(ctrlF, "searchLocal");
        codeArea.getActionMap().put("searchLocal", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                showLocalSearchDialog();
            }
        });

        // 全局搜索
        KeyStroke ctrlShiftF = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
        codeArea.getInputMap().put(ctrlShiftF, "searchGlobal");
        codeArea.getActionMap().put("searchGlobal", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                showGlobalSearchDialog();
            }
        });
    }

    private void showLocalSearchDialog() {
        if (currentSearchDialog != null && currentSearchDialog.isVisible()) {
            currentSearchDialog.setVisible(true);
            return;
        }

        currentSearchDialog = new JDialog(this, "查找", false);
        JTextField searchField = new JTextField(20);
        JButton nextButton = new JButton("下一个");
        JButton prevButton = new JButton("上一个");

        searchField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    searchInText(searchField.getText(), true);
                }
            }
        });

        nextButton.addActionListener(e -> searchInText(searchField.getText(), true));
        prevButton.addActionListener(e -> searchInText(searchField.getText(), false));

        JPanel panel = new JPanel();
        panel.add(searchField);
        panel.add(prevButton);
        panel.add(nextButton);

        currentSearchDialog.add(panel);
        currentSearchDialog.pack();
        currentSearchDialog.setLocationRelativeTo(this);

        Point editorLocation = codeArea.getLocationOnScreen();
        int editorWidth = codeArea.getWidth();
        int dialogWidth = currentSearchDialog.getWidth();

        int offsetX = -5;
        int offsetY = 0;

        //currentSearchDialog.setLocation(editorLocation.x + editorWidth - dialogWidth - offsetX, editorLocation.y + offsetY);
        currentSearchDialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                closeSearchDialog();
            }
        });
        currentSearchDialog.setVisible(true);
    }

    private void closeSearchDialog() {
        if (currentSearchDialog != null) {
            currentSearchDialog.dispose();
            currentSearchDialog = null;
        }
    }

    private void searchInText(String pattern, boolean forward) {
        String text = codeArea.getText();
        int caret = codeArea.getCaretPosition();

        if (forward) {
            int index = text.indexOf(pattern, caret);
            if (index != -1) {
                codeArea.select(index, index + pattern.length());
            } else {
                int reverseIndex = text.lastIndexOf(pattern, caret - pattern.length());
                if (reverseIndex != -1) {
                    codeArea.select(reverseIndex, reverseIndex + pattern.length());
                }
            }
        } else {
            int index = text.lastIndexOf(pattern, caret - pattern.length());
            if (index != -1) {
                codeArea.select(index, index + pattern.length());
            } else {
                int forwardIndex = text.indexOf(pattern, caret);
                if (forwardIndex != -1) {
                    codeArea.select(forwardIndex, forwardIndex + pattern.length());
                }
            }
        }
    }

    private void showGlobalSearchDialog() {
        if (currentGlobalSearchDialog != null && currentGlobalSearchDialog.isVisible()) {
            currentGlobalSearchDialog.setVisible(true);
            return;
        }
        currentGlobalSearchDialog = new JDialog(this, "全局搜索", false);
        JTextField searchField = new JTextField(30);
        JButton searchButton = new JButton("搜索");
        DefaultListModel<SearchResult> listModel = new DefaultListModel<>();
        JList<SearchResult> resultList = new JList<>(listModel);

        searchButton.addActionListener(e -> {
            new SwingWorker<Void, SearchResult>() {
                @Override
                protected Void doInBackground() throws Exception {
                    searchInJar(searchField.getText(), listModel);
                    return null;
                }

                @Override
                protected void done() {
                    JOptionPane.showMessageDialog(currentGlobalSearchDialog, "搜索完成！");
                }
            }.execute();
        });

        resultList.addListSelectionListener(e -> {
            SearchResult result = resultList.getSelectedValue();
            if (result != null) {
                navigateToSearchResult(result);
            }
        });

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(searchField, BorderLayout.NORTH);
        panel.add(new JScrollPane(resultList), BorderLayout.CENTER);
        panel.add(searchButton, BorderLayout.SOUTH);

        currentGlobalSearchDialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                closeGlobalSearchDialog();
            }
        });

        currentGlobalSearchDialog.add(panel);
        currentGlobalSearchDialog.setSize(500, 400);
        currentGlobalSearchDialog.setLocationRelativeTo(this);
        currentGlobalSearchDialog.setVisible(true);
    }

    private void closeGlobalSearchDialog() {
        if (currentGlobalSearchDialog != null) {
            currentGlobalSearchDialog.dispose();
            currentGlobalSearchDialog = null;
        }
    }
    private void navigateToSearchResult(SearchResult result) {
        expandTreeToClass(result.filePath);

        SwingUtilities.invokeLater(() -> {
            try {
                codeArea.setCaretPosition(result.position);
                codeArea.moveCaretPosition(result.position + result.matchText.length());
                Rectangle rect = codeArea.modelToView(result.position);
                codeArea.scrollRectToVisible(rect);
                codeArea.requestFocus();
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        });
    }

    private void searchInJar(String pattern, DefaultListModel<SearchResult> listModel) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.isDirectory()) continue;

                String content = "";
                // 区分处理源码和class文件
                if (entry.getName().endsWith(".java")) {
                    try (InputStream is = jar.getInputStream(entry)) {
                        content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8);
                    }
                } else if (entry.getName().endsWith(".class")) {
                    content = decompileClass(entry);
                }
                if (content.contains(pattern)) {
                    int index = content.indexOf(pattern);
                    while (index >= 0) {
                        int finalIndex = index;
                        SwingUtilities.invokeLater(() -> {
                            listModel.addElement(new SearchResult(
                                    entry.getName(),
                                    finalIndex,
                                    pattern
                            ));
                        });
                        index = content.indexOf(pattern, index + 1);
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private String getEntryContent(JarEntry entry) {
        try (JarFile jar = new JarFile(currentJarFile);
             InputStream is = jar.getInputStream(entry)) {
            if (isTextFile(entry.getName())) {
                return new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private static class SearchResult {
        final String filePath;
        final int position;
        final String matchText;

        SearchResult(String path, int pos, String text) {
            filePath = path;
            position = pos;
            matchText = text;
        }

        public String toString() {
            return filePath + ": " + matchText;
        }
    }

    private MethodLink findMethodUnderCursor(Point point) {
        try {
            // 修复坐标转换方法名
            int offset = codeArea.viewToModel(point);

            // 获取当前行的Token列表
            int line = codeArea.getLineOfOffset(offset);
            Token token = codeArea.getTokenListForLine(line);

            // 遍历Tokens找到精确位置
            while (token != null && token.isPaintable()) {
                if (offset >= token.getOffset() && offset <= token.getEndOffset()) {
                    if (token.getType() == Token.IDENTIFIER) {
                        return resolveMethodCall(token, offset);
                    }
                    break;
                }
                token = token.getNextToken();
            }
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
        return null;
    }

    private MethodLink resolveMethodCall(Token token, int offset) {
        try {
            String code = codeArea.getText();
            ParseResult<CompilationUnit> parseResult = javaParser.parse(code);

            if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
                CompilationUnit cu = parseResult.getResult().get();
                Position range2 = new Position(offset, offset);
                return cu.findAll(MethodCallExpr.class).stream()
                        .filter(mce -> mce.getRange()
                                .map(range -> range.contains(range2))
                                .orElse(false)
                        )
                        .findFirst()
                        .map(mce -> {
                            try {
                                String className = mce.resolve().getClassName();
                                String methodName = mce.getNameAsString();
                                String signature = mce.resolve().getSignature();
                                return new MethodLink(className, methodName, signature);
                            } catch (Exception e) {
                                return new MethodLink("Unknown", mce.getNameAsString(), "");
                            }
                        })
                        .orElse(null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private MethodLink resolveMethodCall(MethodCallExpr mce) {
        String methodName = mce.getNameAsString();
        String className = mce.resolve().getClassName(); // 需要处理解析异常
        String signature = mce.resolve().getSignature();
        return new MethodLink(className, methodName, signature);
    }

    private String buildMethodTooltip(MethodLink link) {
        MethodInfo info = methodIndex.get(link.getFullSignature());
        if (info == null) return null;

        return "<html><div style='max-width:500px;padding:5px;'>" +
                "<b style='color:#569CD6;'>" + info.className + "</b>" +
                "<b style='color:#DCDCAA;'>." + info.methodName + "</b>" +
                "<span style='color:#9CDCFE;'>" + info.parameters + "</span><br/>" +
                "<div style='color:#CE9178;margin-top:5px;'>" +
                (info.javadoc != null ? info.javadoc : "No documentation available") +
                "</div></div></html>";
    }

    private void handleMethodClick(MouseEvent e) {
        MethodLink link = findMethodUnderCursor(e.getPoint());
        if (link == null) return;

        // 支持左键/中键跳转
        if (SwingUtilities.isLeftMouseButton(e) ||
                SwingUtilities.isMiddleMouseButton(e)) {
            navigateToMethod(link);
        }
    }

    private void navigateToMethod(MethodLink link) {
        // 转换为内部路径格式
        String sourcePath = link.className.replace('.', '/') + ".java";
        String classPath = link.className.replace('.', '/') + ".class";
        if (entryExistsInJar(sourcePath)) {
            expandTreeToClass(sourcePath);
            loadAndNavigateInSource(sourcePath, link.methodName);
        } else {
            expandTreeToClass(classPath);
            SwingUtilities.invokeLater(() -> {
                String code = codeArea.getText();
                String pattern = Pattern.quote(link.methodName + link.signature);
                Matcher matcher = Pattern.compile(pattern).matcher(code);
                if (matcher.find()) {
                    codeArea.setCaretPosition(matcher.start());
                    codeArea.moveCaretPosition(matcher.end());
                    codeArea.scrollRectToVisible(codeArea.getVisibleRect());
                }
            });
        }
    }

    private boolean entryExistsInJar(String path) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            return jar.getEntry(path) != null;
        } catch (Exception e) {
            return false;
        }
    }

    private void loadAndNavigateInSource(String sourcePath, String methodName) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            JarEntry entry = jar.getJarEntry(sourcePath);
            try (InputStream is = jar.getInputStream(entry)) {
                String sourceCode = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8);
                codeArea.setText(sourceCode);

                // 在源码中定位方法
                Pattern pattern = Pattern.compile("\\b" + methodName + "\\s*\\(");
                Matcher matcher = pattern.matcher(sourceCode);
                if (matcher.find()) {
                    codeArea.setCaretPosition(matcher.start());
                    codeArea.moveCaretPosition(matcher.end());
                }
            }
        } catch (Exception ex) {
            showError("源码加载错误", ex.getMessage());
        }
    }

    private void expandTreeToClass(String classPath) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) fileTree.getModel().getRoot();
        String[] pathSegments = classPath.split("/");

        // 递归查找节点
        DefaultMutableTreeNode targetNode = findNode(root, pathSegments, 0);

        if (targetNode != null) {
            TreePath path = new TreePath(targetNode.getPath());
            fileTree.expandPath(path);
            fileTree.setSelectionPath(path);
            fileTree.scrollPathToVisible(path);
        }
    }

    private DefaultMutableTreeNode findNode(DefaultMutableTreeNode parent, String[] pathSegments, int depth) {
        if (depth >= pathSegments.length) {
            return parent;
        }

        String target = pathSegments[depth].toLowerCase();
        Enumeration<?> children = parent.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            String nodeName = child.getUserObject().toString().toLowerCase();
            if (nodeName.startsWith(target)) {
                DefaultMutableTreeNode found = findNode(child, pathSegments, depth + 1);
                if (found != null) return found;
            }
        }
        return null;
    }

    private static class MethodLink {
        final String className;
        final String methodName;
        final String signature;

        MethodLink(String className, String methodName, String signature) {
            this.className = className;
            this.methodName = methodName;
            this.signature = signature;
        }

        String getFullSignature() {
            return className + "#" + methodName + signature;
        }
    }


    /**
     * 处理超链接点击事件
     *
     * @param url 点击的链接 URL
     */
    private void handleHyperlinkClick(URL url) {
        if (url == null) return;

        try {
            String href = url.toString();
            if (href.startsWith("#")) {
                // 处理内部链接（如 {@link #methodName}）
                String target = href.substring(1); // 去掉 # 符号
                navigateToMethod(target); // 跳转到目标方法
            } else {
                // 处理外部链接（如 {@link java.lang.String}）
                Desktop.getDesktop().browse(new URI(href));
            }
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "无法打开链接: " + url, "错误", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * 跳转到指定方法
     *
     * @param methodName 方法名称
     */
    private void navigateToMethod(String methodName) {
        String text = codeArea.getText();
        int index = text.indexOf(methodName + "("); // 查找方法定义

        if (index >= 0) {
            codeArea.setCaretPosition(index); // 移动光标到方法定义处
            codeArea.requestFocusInWindow(); // 聚焦编辑器
        } else {
            JOptionPane.showMessageDialog(this, "未找到方法: " + methodName, "提示", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * 获取光标位置的 Javadoc 注释
     *
     * @return Javadoc 注释内容，如果光标不在 Javadoc 上则返回 null
     */
    private String getJavadocAtCursor() {
        try {
            int offset = codeArea.getCaretPosition(); // 获取光标位置
            Token token = codeArea.getTokenListForLine(codeArea.getCaretLineNumber());

            // 遍历当前行的 Token，查找 Javadoc 注释
            while (token != null && token.isPaintable()) {
                if (token.getType() == Token.COMMENT_DOCUMENTATION) {
                    // 检查光标是否在 Javadoc 范围内
                    if (offset >= token.getOffset() && offset <= token.getEndOffset()) {
                        return token.getLexeme(); // 返回 Javadoc 内容
                    }
                }
                token = token.getNextToken();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null; // 未找到 Javadoc
    }

    /**
     * 格式化 Javadoc 注释为 HTML
     *
     * @param rawJavadoc 原始 Javadoc 注释
     * @return 格式化后的 HTML 内容
     */
    private String formatJavadoc(String rawJavadoc) {
        // 替换 Javadoc 标签为高亮样式
        String formatted = rawJavadoc
                .replace("@param", "<b style='color: #CC7832;'>@param</b>")
                .replace("@return", "<b style='color: #CC7832;'>@return</b>")
                .replace("@throws", "<b style='color: #CC7832;'>@throws</b>")
                .replace("@see", "<b style='color: #CC7832;'>@see</b>")
                .replace("{@link", "<a style='color: #6897BB; text-decoration: none;' href='{@link'>")
                .replace("{@code", "<code style='color: #E0E0E0; background: #3A3A3A; padding: 2px;'>")
                .replace("\n", "<br>"); // 换行符替换为 HTML 换行

        // 包裹 HTML 结构
        return "<html><body style='font-family: JetBrains Mono, Consolas; color: #629755; font-size: 12px;'>" +
                formatted +
                "</body></html>";
    }

    private SyntaxScheme configureIDEATheme(SyntaxScheme scheme) {
        // 基础颜色配置
        Color background = new Color(0x2B2B2B);
        Color foreground = new Color(0xE0E0E0);

        // 设置全局默认样式
        Style defaultStyle = scheme.getStyle(Token.NULL);
        defaultStyle.foreground = foreground;
        defaultStyle.background = background;

        // IDEA 风格关键颜色配置
        setTokenStyle(scheme, Token.RESERVED_WORD,    0xCC7832);
        setTokenStyle(scheme, Token.SEPARATOR,       0x4EC9B0);
        setTokenStyle(scheme, Token.OPERATOR,        0xFFD700);
        setTokenStyle(scheme, Token.IDENTIFIER,      0xE0E0E0);
        setTokenStyle(scheme, Token.LITERAL_STRING_DOUBLE_QUOTE, 0x6A8759);
        setTokenStyle(scheme, Token.LITERAL_NUMBER_DECIMAL_INT, 0x6897BB);
        setTokenStyle(scheme, Token.COMMENT_EOL,         0x6A8759);
        setTokenStyle(scheme, Token.COMMENT_MULTILINE,   0x6A8759);
        setTokenStyle(scheme, Token.COMMENT_DOCUMENTATION, 0x629755);
        setTokenStyle(scheme, Token.ANNOTATION,      0xBBB529);
        setTokenStyle(scheme, Token.FUNCTION,        0xFFC66D);
        setTokenStyle(scheme, Token.DATA_TYPE,       0xE8BF6A);

        // ======== 跨语言通用配置 ========
        // XML/HTML 标签（解决 </RollingFile> 颜色问题）
        setTokenStyle(scheme, Token.MARKUP_TAG_NAME,    0xE8BF6A);
        setTokenStyle(scheme, Token.MARKUP_TAG_DELIMITER, 0xCC7832);
        setTokenStyle(scheme, Token.MARKUP_ENTITY_REFERENCE, 0x6897BB);

        // 正则表达式
        setTokenStyle(scheme, Token.REGEX,              0xD25252);

        // ======== 增强Java配置 ========
        setTokenStyle(scheme, Token.RESERVED_WORD_2,    0xCC7832);
        setTokenStyle(scheme, Token.ANNOTATION,         0xBBB529);


        // 设置当前行高亮
        codeArea.setHighlightCurrentLine(true);
        codeArea.setCurrentLineHighlightColor(new Color(0x323232));

        return scheme;
    }


    private void setTokenStyle(SyntaxScheme scheme, int tokenType, int rgb) {
        Style style = scheme.getStyle(tokenType);
        style.foreground = new Color(rgb);
        // 如果需要可以设置粗体/斜体
        // style.fontFlags = Font.BOLD;
    }

    /****/
    private void openJarFile() {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
            public boolean accept(File f) {
                return f.getName().endsWith(".jar") || f.isDirectory();
            }
            public String getDescription() { return "JAR Files (*.jar)"; }
        });

        if(chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            loadJar(chooser.getSelectedFile());
        }
    }

    /****/
    private void loadJar(File jarFile) {
        currentJarFile = jarFile;
        try (JarFile jar = new JarFile(jarFile)) {
            // 新增源码文件优先标记
            Set<String> sourceFiles = new HashSet<>();

            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                if (name.endsWith(".java")) {
                    sourceFiles.add(name.replace(".java", ".class"));
                }
            }

            entries = jar.entries(); // 重新遍历
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                // 忽略已被源码覆盖的class文件
                if (name.endsWith(".class") && sourceFiles.contains(name)) continue;
                addEntryToTree(root, name);
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this,
                    "Error loading JAR: " + ex.getMessage(),
                    "Error",
                    JOptionPane.ERROR_MESSAGE
            );
        }
    }

    /****/
    private void addEntryToTree(DefaultMutableTreeNode parent, String path) {
        String[] segments = path.split("/");
        DefaultMutableTreeNode current = parent;

        for (String segment : segments) {
            DefaultMutableTreeNode child = findChild(current, segment);
            if (child == null) {
                child = new DefaultMutableTreeNode(segment);
                current.add(child);
            }
            current = child;
        }
    }

    /****/
    private DefaultMutableTreeNode findChild(DefaultMutableTreeNode parent, String name) {
        Enumeration<?> children = parent.children();
        while (children.hasMoreElements()) {
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
            if (child.getUserObject().equals(name)) {
                return child;
            }
        }
        return null;
    }

    /****/
    private class TreeSelectionHandler implements TreeSelectionListener {
        public void valueChanged(TreeSelectionEvent e) {
            TreePath path = fileTree.getSelectionPath();
            if (path != null) {
                String entryPath = buildEntryPath(path);
                handleSelection(entryPath);
            }
        }

        private String buildEntryPath(TreePath path) {
            StringBuilder sb = new StringBuilder();
            Object[] nodes = path.getPath();
            for (int i=1; i<nodes.length; i++) { // Skip root
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodes[i];
                sb.append(node.getUserObject());
                if (i != nodes.length-1) sb.append("/");
            }
            return sb.toString();
        }
    }

    private boolean isTextFile(String name) {
        String[] exts = {".txt", ".xml", ".mf", ".MF"};
        for (String ext : exts) {
            if (name.toLowerCase().endsWith(ext)) return true;
        }
        return false;
    }

    /****/
    private void handleSelection(String entryPath) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            JarEntry entry = jar.getJarEntry(entryPath);
            if (entry == null) return;

            if (entryPath.endsWith(".class")) {
                decompileWithCFR(entry);
                buildMethodIndex(entryPath);
            } else if (entryPath.endsWith(".java")) {
                try (InputStream is = jar.getInputStream(entry)) {
                    String sourceCode = new String(IOUtils.toByteArray(is), "UTF-8");
                    SwingUtilities.invokeLater(() -> {
                        codeArea.setText(sourceCode);
                        codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
                        codeArea.revalidate();
                    });
                    contentPane.setSelectedIndex(0);
                }
            } else if (entryPath.toLowerCase().endsWith(".xml")) {
                loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_XML);
            } else if (isTextFile(entryPath)) {
                loadTextFile(jar, entry, SyntaxConstants.SYNTAX_STYLE_NONE);
            } else if (isImageFile(entryPath)) {
                showImage(entry);
            }
        } catch (Exception ex) {
            showError("Selection Error", ex.getMessage());
        }
    }

    private void buildMethodIndex(String entryPath) {
        try {
            String code = "";
            if (entryPath.endsWith(".java")) {
                // 直接读取源码
                try (InputStream is = new JarFile(currentJarFile).getInputStream(new JarEntry(entryPath))) {
                    code = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8);
                }
            } else {
                code = codeArea.getText(); // 反编译内容
            }
            ParseResult<CompilationUnit> parseResult = javaParser.parse(code);
            parseResult.ifSuccessful(cu -> {
                cu.findAll(MethodDeclaration.class).forEach(md -> {

                    MethodInfo info = new MethodInfo();
                    info.className = entryPath.replace("/", ".")
                            .replace(".java", "")
                            .replace(".class", "");

                    info.methodName = md.getNameAsString();
                    info.parameters = md.getParameters().toString();
                    info.javadoc = md.getJavadoc().map(d -> d.getDescription().toText()).orElse(null);

                    methodIndex.put(info.getSignature(), info);
                });
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //++ 方法信息类
    private static class MethodInfo {
        String className;
        String methodName;
        String parameters;
        String javadoc;

        String getSignature() {
            return className + "#" + methodName + parameters;
        }
    }

    private void loadTextFile(JarFile jar, JarEntry entry, String syntaxStyle) throws IOException {
        try (InputStream is = jar.getInputStream(entry)) {
            String content = new String(IOUtils.toByteArray(is), StandardCharsets.UTF_8);
            SwingUtilities.invokeLater(() -> {
                codeArea.setText(content);
                codeArea.setSyntaxEditingStyle(syntaxStyle);
                codeArea.revalidate();

                if (entry.getName().toUpperCase().endsWith("MANIFEST.MF")) {
                    codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE);
                }
            });
        }
        contentPane.setSelectedIndex(0);
    }

    private void decompileWithCFR(JarEntry entry) {
        try (JarFile jar = new JarFile(currentJarFile)) {
            CfrDriver driver = new CfrDriver.Builder()
                    .withClassFileSource(new JarClassFileSource(jar))
                    .withOutputSink(new CFROutputSinkFactory(codeArea))
                    .build();

            driver.analyse(Collections.singletonList(
                    entry.getName()
            ));

            SwingUtilities.invokeLater(() -> {
                codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
                codeArea.revalidate();
            });
            contentPane.setSelectedIndex(0);
        } catch (Exception ex) {
            showError("CFR Decompilation Error", ex.getMessage());
        }
    }

    /****/
    private void showImage(JarEntry entry) {
        try (InputStream is = new JarFile(currentJarFile).getInputStream(entry)) {
            ImageIcon icon = new ImageIcon(IOUtils.toByteArray(is));
            imageLabel.setIcon(icon);
            contentPane.setSelectedIndex(1);
        } catch (Exception ex) {
            showError("Image Error", ex.getMessage());
        }
    }

    /****/
    private boolean isImageFile(String name) {
        String[] exts = {".png", ".jpg", ".gif"};
        for (String ext : exts) {
            if (name.toLowerCase().endsWith(ext)) return true;
        }
        return false;
    }

    /****/
    private void showError(String title, String message) {
        JOptionPane.showMessageDialog(
                this,
                message,
                title,
                JOptionPane.ERROR_MESSAGE
        );
    }

    /****/
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                // 使用Darcula主题（需添加FlatLaf依赖）
                UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            ModernJarViewer viewer = new ModernJarViewer(null);
            viewer.setVisible(true);
        });
    }
}