package com.huangzj.protoplugin.devtools;

import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.JBUI;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;

public final class DevToolsPanel extends JPanel {

    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final Font MONO_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 13);
    private static final Font TITLE_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 16);
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static final String ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static final Color ACCENT = new JBColor(new Color(0x2675BF), new Color(0x589DF6));
    private static final Color SUBTITLE = new JBColor(new Color(0x6B7280), new Color(0x9CA3AF));
    private static final Color CAT_BG = new JBColor(new Color(0xF5F5F5), new Color(0x2B2D30));
    private static final Color CAT_SEL_BG = new JBColor(new Color(0xD0E4F7), new Color(0x3C4F6A));
    private static final Color CAT_TEXT = new JBColor(new Color(0x333333), new Color(0xBBBBBB));
    private static final Color CAT_SEL_TEXT = new JBColor(new Color(0x1565C0), new Color(0x90CAF9));

    private static final Map<String, List<String>> CATEGORIES = new LinkedHashMap<>();

    static {
        CATEGORIES.put("\uD83D\uDD11 编码解码", Arrays.asList("Base64", "URL", "HTML", "Unicode", "转义", "全角半角"));
        CATEGORIES.put("\uD83D\uDCC4 格式化", Arrays.asList("JSON", "SQL", "XML", "YAML", "Markdown"));
        CATEGORIES.put("\uD83D\uDD28 生成", Arrays.asList("UUID", "随机字符串", "随机数", "Lorem", "JSON Schema"));
        CATEGORIES.put("\u23F0 时间", Arrays.asList("时间戳", "时区", "日期计算", "Cron"));
        CATEGORIES.put("\uD83C\uDF10 网络", Arrays.asList("IP", "URL解析"));
        CATEGORIES.put("\uD83D\uDCDD 文本处理", Arrays.asList("正则", "文本对比", "文本统计", "大小写", "换行符", "列表工具"));
        CATEGORIES.put("\uD83D\uDD12 安全", Arrays.asList("哈希", "JWT", "密码强度"));
        CATEGORIES.put("\uD83D\uDD04 转换", Arrays.asList("进制", "颜色", "大小端", "文件大小", "Unix权限", "CSV/JSON"));
        CATEGORIES.put("\uD83D\uDCD6 参考", Arrays.asList("ASCII", "HTTP状态码", "MIME", "正则速查"));
    }

    private final DefaultListModel<String> catListModel = new DefaultListModel<>();
    private final JBList<String> catList = new JBList<>(catListModel);
    private final JTabbedPane toolTabs = new JTabbedPane(JTabbedPane.TOP);
    private final Map<String, JPanel> panelCache = new HashMap<>();

    public DevToolsPanel() {
        super(new BorderLayout());
        setBorder(JBUI.Borders.empty(4));

        initCategoryList();
        initToolTabs();

        JPanel categoryPanel = createCategoryPanel();
        categoryPanel.setMinimumSize(new Dimension(120, 0));
        categoryPanel.setPreferredSize(new Dimension(150, 0));

        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, categoryPanel, toolTabs);
        split.setResizeWeight(0.15);
        split.setDividerSize(4);
        split.setBorder(null);
        add(split, BorderLayout.CENTER);

        if (catListModel.size() > 0) {
            catList.setSelectedIndex(0);
        }
    }

    private void initCategoryList() {
        for (String cat : CATEGORIES.keySet()) {
            catListModel.addElement(cat);
        }
        catList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        catList.setCellRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                                                           boolean isSelected, boolean cellHasFocus) {
                JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                label.setBorder(JBUI.Borders.empty(8, 12, 8, 12));
                label.setFont(label.getFont().deriveFont(Font.PLAIN, 13f));
                if (isSelected) {
                    label.setBackground(CAT_SEL_BG);
                    label.setForeground(CAT_SEL_TEXT);
                } else {
                    label.setBackground(CAT_BG);
                    label.setForeground(CAT_TEXT);
                }
                label.setOpaque(true);
                return label;
            }
        });
        catList.addListSelectionListener(e -> {
            if (e.getValueIsAdjusting()) return;
            String cat = catList.getSelectedValue();
            if (cat != null) {
                switchCategory(cat);
            }
        });
    }

    private JPanel createCategoryPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBackground(CAT_BG);
        JLabel header = new JLabel("  工具分类", SwingConstants.LEFT);
        header.setFont(header.getFont().deriveFont(Font.BOLD, 14f));
        header.setBorder(JBUI.Borders.empty(8, 8, 4, 8));
        header.setForeground(CAT_TEXT);
        panel.add(header, BorderLayout.NORTH);

        JBScrollPane scroll = new JBScrollPane(catList);
        scroll.setBorder(null);
        scroll.getViewport().setBackground(CAT_BG);
        panel.add(scroll, BorderLayout.CENTER);
        return panel;
    }

    private void initToolTabs() {
        toolTabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
        toolTabs.setBackground(new JBColor(new Color(0xFAFAFA), new Color(0x2B2D30)));
    }

    private void switchCategory(String category) {
        toolTabs.removeAll();
        List<String> tools = CATEGORIES.get(category);
        if (tools == null) return;
        for (String toolName : tools) {
            JPanel panel = getOrCreatePanel(toolName);
            toolTabs.addTab(toolName, panel);
        }
    }

    private JPanel getOrCreatePanel(String toolName) {
        return panelCache.computeIfAbsent(toolName, this::createPanelForTool);
    }

    private JPanel createPanelForTool(String toolName) {
        switch (toolName) {
            case "Base64": return createBase64Panel();
            case "URL": return createUrlPanel();
            case "HTML": return createHtmlEntityPanel();
            case "Unicode": return createUnicodePanel();
            case "转义": return createStringEscapePanel();
            case "全角半角": return createFullHalfWidthPanel();
            case "JSON": return createJsonPanel();
            case "SQL": return createSqlPanel();
            case "XML": return createXmlPanel();
            case "YAML": return createYamlPanel();
            case "Markdown": return createMarkdownPanel();
            case "UUID": return createUuidPanel();
            case "随机字符串": return createRandomPanel();
            case "随机数": return createRandomNumberPanel();
            case "Lorem": return createLoremPanel();
            case "JSON Schema": return createJsonSchemaPanel();
            case "时间戳": return createTimestampPanel();
            case "时区": return createTimezonePanel();
            case "日期计算": return createDateCalcPanel();
            case "Cron": return createCronPanel();
            case "IP": return createIpPanel();
            case "URL解析": return createUrlParserPanel();
            case "正则": return createRegexPanel();
            case "文本对比": return createDiffPanel();
            case "文本统计": return createTextStatsPanel();
            case "大小写": return createCasePanel();
            case "换行符": return createLineEndingPanel();
            case "列表工具": return createListToolsPanel();
            case "哈希": return createHashPanel();
            case "进制": return createBaseConvertPanel();
            case "颜色": return createColorPanel();
            case "JWT": return createJwtPanel();
            case "大小端": return createEndianPanel();
            case "文件大小": return createFileSizePanel();
            case "Unix权限": return createChmodPanel();
            case "CSV/JSON": return createCsvJsonPanel();
            case "ASCII": return createAsciiPanel();
            case "HTTP状态码": return createHttpStatusPanel();
            case "MIME": return createMimePanel();
            case "正则速查": return createRegexCheatPanel();
            case "密码强度": return createPasswordPanel();
            default: return new JPanel();
        }
    }

    // ---- helpers ----

    private static JPanel paddedPanel() {
        JPanel p = new JPanel();
        p.setBorder(JBUI.Borders.empty(12));
        p.setBackground(new JBColor(new Color(0xFAFAFA), new Color(0x2B2D30)));
        return p;
    }

    private static JTextArea createOutputArea(String placeholder) {
        JBTextArea area = new JBTextArea();
        area.setEditable(false);
        area.setFont(MONO_FONT);
        area.setLineWrap(true);
        area.setWrapStyleWord(true);
        area.getEmptyText().setText(placeholder);
        area.setBackground(new JBColor(new Color(0xF8F9FA), new Color(0x1E1E1E)));
        area.setForeground(new JBColor(new Color(0x333333), new Color(0xD4D4D4)));
        return area;
    }

    private static JTextArea createInputArea(String placeholder) {
        JBTextArea area = new JBTextArea();
        area.setFont(MONO_FONT);
        area.setLineWrap(true);
        area.setWrapStyleWord(true);
        area.getEmptyText().setText(placeholder);
        area.setBackground(new JBColor(new Color(0xFFFFFF), new Color(0x252526)));
        area.setForeground(new JBColor(new Color(0x333333), new Color(0xD4D4D4)));
        area.setCaretColor(new JBColor(new Color(0x333333), new Color(0xD4D4D4)));
        return area;
    }

    private static final Color BTN_PRIMARY = new Color(0x2675BF);
    private static final Color BTN_SUCCESS = new Color(0x27AE60);
    private static final Color BTN_DANGER = new Color(0xE74C3C);
    private static final Color BTN_WARNING = new Color(0xE67E22);
    private static final Color BTN_SECONDARY = new Color(0x6B7280);
    private static final Color BTN_INFO = new Color(0x8E44AD);

    private static JButton createStyledBtn(String text, Color bg, String tooltip, java.awt.event.ActionListener listener) {
        JButton b = new JButton(text) {
            @Override
            protected void paintComponent(Graphics g) {
                Graphics2D g2 = (Graphics2D) g.create();
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                if (getModel().isPressed()) {
                    g2.setColor(getBackground().darker());
                } else if (getModel().isRollover()) {
                    g2.setColor(getBackground().brighter());
                } else {
                    g2.setColor(getBackground());
                }
                g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8);
                g2.dispose();
                super.paintComponent(g);
            }
        };
        b.setOpaque(false);
        b.setBackground(bg);
        b.setForeground(Color.WHITE);
        b.setFont(b.getFont().deriveFont(Font.BOLD, 12f));
        b.setBorder(JBUI.Borders.empty(5, 14));
        b.setFocusPainted(false);
        b.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
        b.setToolTipText(tooltip);
        if (listener != null) b.addActionListener(listener);
        return b;
    }

    private static JButton toolBtn(String text, String tooltip) {
        return createStyledBtn(text, BTN_SECONDARY, tooltip, null);
    }

    private static JButton toolBtn(String text, String tooltip, java.awt.event.ActionListener listener) {
        return createStyledBtn(text, BTN_PRIMARY, tooltip, listener);
    }

    private static JButton styledBtn(String text, Color bg, java.awt.event.ActionListener listener) {
        return createStyledBtn(text, bg, null, listener);
    }

    private static int parseInt(String text, int defaultValue) {
        try {
            return Integer.parseInt(text.trim());
        } catch (Exception e) {
            return defaultValue;
        }
    }

    private static JButton copyBtn(JTextArea source) {
        return createStyledBtn("复制", BTN_INFO, "复制结果到剪贴板", e -> {
            String text = source.getText();
            if (text != null && !text.isEmpty()) {
                Toolkit.getDefaultToolkit().getSystemClipboard()
                        .setContents(new StringSelection(text), null);
            }
        });
    }

    private static void showError(String msg) {
        JOptionPane.showMessageDialog(null, msg, "错误", JOptionPane.ERROR_MESSAGE);
    }

    private static JPanel ioPanel(JTextArea input, JTextArea output) {
        JPanel p = new JPanel(new GridLayout(2, 1, 0, 8));
        p.setOpaque(false);
        p.add(wrapScroll(input, "输入"));
        p.add(wrapScroll(output, "输出"));
        return p;
    }

    private static JScrollPane wrapScroll(JTextArea area, String title) {
        JScrollPane sp = new JBScrollPane(area);
        sp.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(new JBColor(new Color(0xDDDDDD), new Color(0x444444))),
                title, TitledBorder.LEFT, TitledBorder.TOP,
                new Font(Font.SANS_SERIF, Font.PLAIN, 11),
                new JBColor(new Color(0x6B7280), new Color(0x9CA3AF))));
        return sp;
    }

    private static JPanel btnRow(JButton... btns) {
        JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 4));
        p.setOpaque(false);
        p.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40));
        for (JButton b : btns) p.add(b);
        return p;
    }

    // ==================== Base64 ====================

    private JPanel createBase64Panel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Base64 编解码");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("支持标准 Base64 和 URL 安全格式");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入原始文本...");
        JTextArea output = createOutputArea("编码/解码结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("编码", "标准 Base64 编码", e -> {
                    String t = input.getText();
                    if (!t.isEmpty()) output.setText(Base64.getEncoder().encodeToString(t.getBytes(StandardCharsets.UTF_8)));
                }),
                toolBtn("解码", "标准 Base64 解码", e -> {
                    String t = input.getText().trim();
                    if (t.isEmpty()) return;
                    try { output.setText(new String(Base64.getDecoder().decode(t), StandardCharsets.UTF_8)); }
                    catch (Exception ex) { showError("无效的 Base64 编码"); }
                }),
                toolBtn("URL安全编码", "URL 安全的 Base64 编码", e -> {
                    String t = input.getText();
                    if (!t.isEmpty()) output.setText(Base64.getUrlEncoder().withoutPadding().encodeToString(t.getBytes(StandardCharsets.UTF_8)));
                }),
                toolBtn("URL安全解码", "URL 安全的 Base64 解码", e -> {
                    String t = input.getText().trim();
                    if (t.isEmpty()) return;
                    try { output.setText(new String(Base64.getUrlDecoder().decode(t), StandardCharsets.UTF_8)); }
                    catch (Exception ex) { showError("无效的 URL 安全 Base64"); }
                }),
                toolBtn("交换", "交换输入输出", e -> { String t = input.getText(); input.setText(output.getText()); output.setText(t); }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== URL ====================

    private JPanel createUrlPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("URL 编解码");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("对 URL 中的特殊字符进行编码和解码");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 URL 或文本...");
        JTextArea output = createOutputArea("编码/解码结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("编码", "URL 编码", e -> {
                    try { output.setText(URLEncoder.encode(input.getText(), StandardCharsets.UTF_8)); }
                    catch (Exception ex) { showError("编码失败"); }
                }),
                toolBtn("解码", "URL 解码", e -> {
                    try { output.setText(URLDecoder.decode(input.getText().trim(), StandardCharsets.UTF_8)); }
                    catch (Exception ex) { showError("解码失败"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== HTML Entity ====================

    private JPanel createHtmlEntityPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("HTML 实体编解码");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("将 HTML 特殊字符转换为实体编码，或反向还原");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 HTML 或文本...");
        JTextArea output = createOutputArea("编码/解码结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("编码", "HTML 实体编码", e -> {
                    String t = input.getText();
                    output.setText(t.replace("&", "&amp;").replace("<", "&lt;")
                            .replace(">", "&gt;").replace("\"", "&quot;")
                            .replace("'", "&#39;"));
                }),
                toolBtn("解码", "HTML 实体解码", e -> {
                    String t = input.getText();
                    output.setText(t.replace("&lt;", "<").replace("&gt;", ">")
                            .replace("&quot;", "\"").replace("&#39;", "'")
                            .replace("&amp;", "&"));
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Unicode ====================

    private JPanel createUnicodePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Unicode 转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在中文/符号与 \\uXXXX 编码之间互相转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本或 \\u 编码...");
        JTextArea output = createOutputArea("转换结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转 \\u", "文本转 Unicode 编码", e -> {
                    StringBuilder sb = new StringBuilder();
                    for (char c : input.getText().toCharArray()) {
                        if (c > 127) sb.append(String.format("\\u%04X", (int) c));
                        else sb.append(c);
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("\\u 转文本", "Unicode 编码转文本", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    int i = 0;
                    while (i < t.length()) {
                        if (t.charAt(i) == '\\' && i + 5 < t.length() && t.charAt(i + 1) == 'u') {
                            sb.append((char) Integer.parseInt(t.substring(i + 2, i + 6), 16));
                            i += 6;
                        } else {
                            sb.append(t.charAt(i));
                            i++;
                        }
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== String Escape ====================

    private JPanel createStringEscapePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("字符串转义");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("Java / JavaScript / JSON 字符串的转义与还原");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("转义/还原结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("Java 转义", "Java 字符串转义", e -> {
                    String t = input.getText();
                    output.setText(t.replace("\\", "\\\\").replace("\"", "\\\"")
                            .replace("\n", "\\n").replace("\r", "\\r")
                            .replace("\t", "\\t"));
                }),
                toolBtn("Java 还原", "Java 字符串还原", e -> {
                    String t = input.getText();
                    output.setText(t.replace("\\t", "\t").replace("\\r", "\r")
                            .replace("\\n", "\n").replace("\\\"", "\"")
                            .replace("\\\\", "\\"));
                }),
                toolBtn("JSON 转义", "JSON 字符串转义", e -> {
                    String t = input.getText();
                    output.setText(t.replace("\\", "\\\\").replace("\"", "\\\"")
                            .replace("\n", "\\n").replace("\r", "\\r")
                            .replace("\t", "\\t"));
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Full/Half Width ====================

    private JPanel createFullHalfWidthPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("全角半角转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在全角与半角字符之间互相转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("转换结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("全角转半角", "全角字符转半角", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    for (char c : t.toCharArray()) {
                        if (c >= 0xFF01 && c <= 0xFF5E) sb.append((char) (c - 0xFEE0));
                        else if (c == 0x3000) sb.append(' ');
                        else sb.append(c);
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("半角转全角", "半角字符转全角", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    for (char c : t.toCharArray()) {
                        if (c >= 0x21 && c <= 0x7E) sb.append((char) (c + 0xFEE0));
                        else if (c == ' ') sb.append((char) 0x3000);
                        else sb.append(c);
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== JSON ====================

    private JPanel createJsonPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("JSON 格式化");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("格式化、压缩和校验 JSON 数据");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 JSON...");
        JTextArea output = createOutputArea("格式化结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("格式化", "美化 JSON", e -> {
                    try {
                        com.google.gson.JsonElement je = com.google.gson.JsonParser.parseString(input.getText());
                        output.setText(new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(je));
                    } catch (Exception ex) { showError("无效的 JSON: " + ex.getMessage()); }
                }),
                toolBtn("压缩", "压缩 JSON 为一行", e -> {
                    try {
                        com.google.gson.JsonElement je = com.google.gson.JsonParser.parseString(input.getText());
                        output.setText(new com.google.gson.Gson().toJson(je));
                    } catch (Exception ex) { showError("无效的 JSON"); }
                }),
                toolBtn("校验", "校验 JSON 格式", e -> {
                    try {
                        com.google.gson.JsonParser.parseString(input.getText());
                        output.setText("JSON 格式有效");
                    } catch (Exception ex) { showError("JSON 格式无效: " + ex.getMessage()); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== SQL ====================

    private JPanel createSqlPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("SQL 格式化");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("简单格式化 SQL 语句，支持 SELECT/INSERT/UPDATE/DELETE");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 SQL...");
        JTextArea output = createOutputArea("格式化结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("格式化", "格式化 SQL", e -> {
                    String sql = input.getText().trim();
                    if (sql.isEmpty()) return;
                    output.setText(formatSql(sql));
                }),
                toolBtn("压缩", "压缩 SQL 为一行", e -> {
                    output.setText(input.getText().trim().replaceAll("\\s+", " "));
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    private static String formatSql(String sql) {
        String[] keywords = {"SELECT", "FROM", "WHERE", "AND", "OR", "ORDER BY", "GROUP BY",
                "HAVING", "LIMIT", "OFFSET", "INSERT INTO", "VALUES", "UPDATE", "SET",
                "DELETE FROM", "JOIN", "LEFT JOIN", "RIGHT JOIN", "INNER JOIN",
                "ON", "UNION", "UNION ALL", "CREATE TABLE", "ALTER TABLE", "DROP TABLE",
                "CREATE INDEX", "DROP INDEX", "INSERT", "INTO"};
        String upper = sql.toUpperCase();
        StringBuilder sb = new StringBuilder();
        int idx = 0;
        while (idx < sql.length()) {
            boolean matched = false;
            for (String kw : keywords) {
                if (upper.startsWith(kw, idx)) {
                    if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n') {
                        sb.append('\n');
                    }
                    sb.append(kw);
                    idx += kw.length();
                    matched = true;
                    break;
                }
            }
            if (!matched) {
                sb.append(sql.charAt(idx));
                idx++;
            }
        }
        return sb.toString().trim();
    }

    // ==================== XML ====================

    private JPanel createXmlPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("XML 格式化");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("格式化 XML 数据");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 XML...");
        JTextArea output = createOutputArea("格式化结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("格式化", "格式化 XML", e -> {
                    try {
                        javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
                        org.w3c.dom.Document doc = factory.newDocumentBuilder().parse(
                                new org.xml.sax.InputSource(new java.io.StringReader(input.getText())));
                        javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
                        transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
                        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
                        java.io.StringWriter sw = new java.io.StringWriter();
                        transformer.transform(new javax.xml.transform.dom.DOMSource(doc), new javax.xml.transform.stream.StreamResult(sw));
                        output.setText(sw.toString());
                    } catch (Exception ex) { showError("XML 解析失败: " + ex.getMessage()); }
                }),
                toolBtn("压缩", "压缩 XML 为一行", e -> {
                    output.setText(input.getText().replaceAll(">\\s+<", "><").trim());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== YAML ====================

    private JPanel createYamlPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("YAML 格式化");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("校验和格式化 YAML 数据");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 YAML...");
        JTextArea output = createOutputArea("格式化结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("格式化", "格式化 YAML", e -> {
                    String t = input.getText().trim();
                    if (t.isEmpty()) return;
                    try {
                        String formatted = formatYaml(t);
                        output.setText(formatted);
                    } catch (Exception ex) {
                        output.setText(t);
                    }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    private static String formatYaml(String yaml) {
        StringBuilder sb = new StringBuilder();
        String[] lines = yaml.split("\n");
        for (String line : lines) {
            String trimmed = line.trim();
            if (trimmed.isEmpty()) {
                sb.append('\n');
                continue;
            }
            int indent = line.length() - line.trim().length();
            if (trimmed.startsWith("- ")) {
                sb.append("  ".repeat(Math.max(0, indent / 2))).append(trimmed).append('\n');
            } else if (trimmed.contains(":")) {
                sb.append("  ".repeat(Math.max(0, indent / 2))).append(trimmed).append('\n');
            } else {
                sb.append("  ".repeat(Math.max(0, indent / 2))).append(trimmed).append('\n');
            }
        }
        return sb.toString().trim();
    }

    // ==================== Markdown ====================

    private JPanel createMarkdownPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Markdown 预览");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("将 Markdown 转换为 HTML 预览");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 Markdown...");
        JTextArea output = createOutputArea("HTML 预览...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转换", "Markdown 转 HTML", e -> {
                    String md = input.getText();
                    if (md.isEmpty()) return;
                    StringBuilder html = new StringBuilder();
                    html.append("<html><body>");
                    for (String line : md.split("\n")) {
                        String trimmed = line.trim();
                        if (trimmed.startsWith("# ")) html.append("<h1>").append(trimmed.substring(2)).append("</h1>");
                        else if (trimmed.startsWith("## ")) html.append("<h2>").append(trimmed.substring(3)).append("</h2>");
                        else if (trimmed.startsWith("### ")) html.append("<h3>").append(trimmed.substring(4)).append("</h3>");
                        else if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) html.append("<li>").append(trimmed.substring(2)).append("</li>");
                        else if (trimmed.startsWith("> ")) html.append("<blockquote>").append(trimmed.substring(2)).append("</blockquote>");
                        else if (trimmed.startsWith("```")) html.append("<pre>");
                        else if (trimmed.equals("```")) html.append("</pre>");
                        else if (trimmed.isEmpty()) html.append("<br>");
                        else html.append("<p>").append(trimmed).append("</p>");
                    }
                    html.append("</body></html>");
                    output.setText(html.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== UUID ====================

    private JPanel createUuidPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("UUID 生成器");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("生成标准 UUID，支持多种输出格式");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea output = createOutputArea("点击下方按钮生成...");
        output.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 16));
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 60));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("标准 UUID", "生成标准格式 UUID", e -> output.setText(UUID.randomUUID().toString())),
                toolBtn("大写", "生成大写 UUID", e -> output.setText(UUID.randomUUID().toString().toUpperCase())),
                toolBtn("无横线", "生成无横线 UUID", e -> output.setText(UUID.randomUUID().toString().replace("-", ""))),
                toolBtn("批量(10个)", "生成 10 个 UUID", e -> {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 10; i++) {
                        if (i > 0) sb.append('\n');
                        sb.append(UUID.randomUUID().toString());
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Random String ====================

    private JPanel createRandomPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("随机字符串生成");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("生成指定长度和字符集的随机字符串");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel lenRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        lenRow.add(new JLabel("长度:"));
        JBTextField lenField = new JBTextField("16");
        lenField.setPreferredSize(new Dimension(60, 26));
        lenRow.add(lenField);
        lenRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(lenRow);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("生成的随机字符串...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 60));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("字母数字", "字母和数字", e -> {
                    int len = parseInt(lenField.getText(), 16);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < len; i++) sb.append(ALPHANUM.charAt(SECURE_RANDOM.nextInt(ALPHANUM.length())));
                    output.setText(sb.toString());
                }),
                toolBtn("全部字符", "所有可打印字符", e -> {
                    int len = parseInt(lenField.getText(), 16);
                    String chars = ALPHANUM + "!@#$%^&*()-_=+[]{}|;:,.<>?";
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < len; i++) sb.append(chars.charAt(SECURE_RANDOM.nextInt(chars.length())));
                    output.setText(sb.toString());
                }),
                toolBtn("数字", "纯数字", e -> {
                    int len = parseInt(lenField.getText(), 16);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < len; i++) sb.append(SECURE_RANDOM.nextInt(10));
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Random Number ====================

    private JPanel createRandomNumberPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("随机数生成");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("生成指定范围内的随机整数");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel rangeRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        rangeRow.add(new JLabel("最小值:"));
        JBTextField minField = new JBTextField("0");
        minField.setPreferredSize(new Dimension(80, 26));
        rangeRow.add(minField);
        rangeRow.add(new JLabel("最大值:"));
        JBTextField maxField = new JBTextField("100");
        maxField.setPreferredSize(new Dimension(80, 26));
        rangeRow.add(maxField);
        rangeRow.add(new JLabel("数量:"));
        JBTextField countField = new JBTextField("1");
        countField.setPreferredSize(new Dimension(60, 26));
        rangeRow.add(countField);
        rangeRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(rangeRow);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("生成的随机数...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 60));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("生成", "生成随机数", e -> {
                    int min = parseInt(minField.getText(), 0);
                    int max = parseInt(maxField.getText(), 100);
                    int count = parseInt(countField.getText(), 1);
                    if (min >= max) { showError("最小值必须小于最大值"); return; }
                    Random rnd = new Random();
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < count; i++) {
                        if (i > 0) sb.append('\n');
                        sb.append(rnd.nextInt(max - min + 1) + min);
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Lorem Ipsum ====================

    private JPanel createLoremPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Lorem Ipsum 生成");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("生成占位文本，用于排版和设计预览");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel optRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        optRow.add(new JLabel("段落数:"));
        JBTextField paraField = new JBTextField("3");
        paraField.setPreferredSize(new Dimension(60, 26));
        optRow.add(paraField);
        optRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(optRow);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("生成的 Lorem Ipsum 文本...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 80));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        String lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

        panel.add(btnRow(
                toolBtn("生成", "生成 Lorem Ipsum", e -> {
                    int paras = parseInt(paraField.getText(), 3);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < paras; i++) {
                        if (i > 0) sb.append("\n\n");
                        sb.append(lorem);
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== JSON Schema ====================

    private JPanel createJsonSchemaPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("JSON Schema 生成");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("从 JSON 数据生成 JSON Schema");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 JSON 数据...");
        JTextArea output = createOutputArea("生成的 Schema...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("生成 Schema", "从 JSON 生成 Schema", e -> {
                    try {
                        com.google.gson.JsonElement je = com.google.gson.JsonParser.parseString(input.getText());
                        Map<String, Object> schema = new LinkedHashMap<>();
                        schema.put("$schema", "http://json-schema.org/draft-07/schema#");
                        schema.put("type", "object");
                        if (je.isJsonObject()) {
                            com.google.gson.JsonObject obj = je.getAsJsonObject();
                            Map<String, Object> properties = new LinkedHashMap<>();
                            for (String key : obj.keySet()) {
                                Map<String, Object> prop = new LinkedHashMap<>();
                                com.google.gson.JsonElement val = obj.get(key);
                                if (val.isJsonPrimitive()) {
                                    if (val.getAsJsonPrimitive().isNumber()) prop.put("type", "number");
                                    else if (val.getAsJsonPrimitive().isBoolean()) prop.put("type", "boolean");
                                    else prop.put("type", "string");
                                } else if (val.isJsonArray()) prop.put("type", "array");
                                else if (val.isJsonObject()) prop.put("type", "object");
                                else prop.put("type", "null");
                                properties.put(key, prop);
                            }
                            schema.put("properties", properties);
                        }
                        output.setText(new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(schema));
                    } catch (Exception ex) { showError("无效的 JSON"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Timestamp ====================

    private JPanel createTimestampPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("时间戳转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("毫秒/秒时间戳与日期时间互相转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel tsRow = new JPanel(new BorderLayout(8, 0));
        tsRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        tsRow.add(new JLabel("时间戳:"), BorderLayout.WEST);
        JBTextField tsField = new JBTextField();
        tsField.getEmptyText().setText("输入毫秒/秒时间戳...");
        tsField.setFont(MONO_FONT);
        tsRow.add(tsField, BorderLayout.CENTER);
        panel.add(tsRow);
        panel.add(Box.createVerticalStrut(8));

        JPanel dtRow = new JPanel(new BorderLayout(8, 0));
        dtRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        dtRow.add(new JLabel("日期时间:"), BorderLayout.WEST);
        JBTextField dtField = new JBTextField();
        dtField.getEmptyText().setText("yyyy-MM-dd HH:mm:ss");
        dtField.setFont(MONO_FONT);
        dtRow.add(dtField, BorderLayout.CENTER);
        panel.add(dtRow);
        panel.add(Box.createVerticalStrut(12));

        panel.add(btnRow(
                toolBtn("时间戳->日期", "时间戳转日期", e -> {
                    try {
                        long ts = Long.parseLong(tsField.getText().trim());
                        if (ts < 100000000000L) ts *= 1000;
                        dtField.setText(DTF.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneId.systemDefault())));
                    } catch (NumberFormatException ex) { showError("无效的时间戳"); }
                }),
                toolBtn("日期->时间戳", "日期转时间戳", e -> {
                    try {
                        LocalDateTime ldt = LocalDateTime.parse(dtField.getText().trim(), DTF);
                        tsField.setText(String.valueOf(ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
                    } catch (Exception ex) { showError("日期格式错误，应为 yyyy-MM-dd HH:mm:ss"); }
                }),
                toolBtn("当前时间", "填入当前时间", e -> {
                    long now = System.currentTimeMillis();
                    tsField.setText(String.valueOf(now));
                    dtField.setText(DTF.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault())));
                }),
                toolBtn("复制时间戳", "复制时间戳", e -> {
                    String t = tsField.getText();
                    if (t != null && !t.isEmpty()) Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(t), null);
                }),
                toolBtn("复制日期", "复制日期", e -> {
                    String t = dtField.getText();
                    if (t != null && !t.isEmpty()) Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(t), null);
                })
        ));

        return panel;
    }

    // ==================== Timezone ====================

    private JPanel createTimezonePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("时区转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在不同时区之间转换时间");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        row.add(new JLabel("源时区:"));
        JComboBox<String> srcZone = new JComboBox<>(new String[]{"Asia/Shanghai", "America/New_York", "Europe/London", "Asia/Tokyo", "UTC"});
        row.add(srcZone);
        row.add(new JLabel("目标时区:"));
        JComboBox<String> dstZone = new JComboBox<>(new String[]{"UTC", "America/New_York", "Europe/London", "Asia/Shanghai", "Asia/Tokyo"});
        row.add(dstZone);
        row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(row);
        panel.add(Box.createVerticalStrut(8));

        JBTextField dtField = new JBTextField();
        dtField.getEmptyText().setText("yyyy-MM-dd HH:mm:ss");
        dtField.setFont(MONO_FONT);
        panel.add(dtField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 50));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转换", "时区转换", e -> {
                    try {
                        LocalDateTime ldt = LocalDateTime.parse(dtField.getText().trim(), DTF);
                        ZonedDateTime src = ldt.atZone(ZoneId.of((String) srcZone.getSelectedItem()));
                        ZonedDateTime dst = src.withZoneSameInstant(ZoneId.of((String) dstZone.getSelectedItem()));
                        output.setText(dst.format(DTF));
                    } catch (Exception ex) { showError("日期格式错误"); }
                }),
                toolBtn("当前时间", "填入当前时间", e -> dtField.setText(DTF.format(LocalDateTime.now()))),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Date Calc ====================

    private JPanel createDateCalcPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("日期计算");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("日期加减和日期差计算");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JPanel row1 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        row1.add(new JLabel("日期:"));
        JBTextField dateField = new JBTextField();
        dateField.getEmptyText().setText("yyyy-MM-dd");
        dateField.setPreferredSize(new Dimension(140, 26));
        row1.add(dateField);
        row1.add(new JLabel("天数:"));
        JBTextField daysField = new JBTextField("0");
        daysField.setPreferredSize(new Dimension(60, 26));
        row1.add(daysField);
        row1.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(row1);
        panel.add(Box.createVerticalStrut(8));

        JPanel row2 = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        row2.add(new JLabel("日期1:"));
        JBTextField d1Field = new JBTextField();
        d1Field.getEmptyText().setText("yyyy-MM-dd");
        d1Field.setPreferredSize(new Dimension(140, 26));
        row2.add(d1Field);
        row2.add(new JLabel("日期2:"));
        JBTextField d2Field = new JBTextField();
        d2Field.getEmptyText().setText("yyyy-MM-dd");
        d2Field.setPreferredSize(new Dimension(140, 26));
        row2.add(d2Field);
        row2.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(row2);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("计算结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 50));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("日期加减", "计算日期加减天数", e -> {
                    try {
                        LocalDateTime dt = LocalDateTime.parse(dateField.getText().trim() + " 00:00:00", DTF);
                        int days = Integer.parseInt(daysField.getText().trim());
                        output.setText("原日期: " + dateField.getText() + "\n加 " + days + " 天: " + dt.plusDays(days).format(DTF).substring(0, 10)
                                + "\n减 " + days + " 天: " + dt.minusDays(days).format(DTF).substring(0, 10));
                    } catch (Exception ex) { showError("日期格式错误"); }
                }),
                toolBtn("日期差", "计算两个日期之差", e -> {
                    try {
                        LocalDateTime d1 = LocalDateTime.parse(d1Field.getText().trim() + " 00:00:00", DTF);
                        LocalDateTime d2 = LocalDateTime.parse(d2Field.getText().trim() + " 00:00:00", DTF);
                        long days = ChronoUnit.DAYS.between(d1, d2);
                        output.setText("相差 " + Math.abs(days) + " 天" + (days < 0 ? " (日期1更早)" : " (日期2更早)"));
                    } catch (Exception ex) { showError("日期格式错误"); }
                }),
                toolBtn("今天", "填入今天日期", e -> {
                    String today = LocalDateTime.now().format(DTF).substring(0, 10);
                    dateField.setText(today);
                    d1Field.setText(today);
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Cron ====================

    private JPanel createCronPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Cron 表达式解析");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("解析 Cron 表达式并计算下次执行时间");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField cronField = new JBTextField();
        cronField.getEmptyText().setText("0 0 12 * * ?");
        cronField.setFont(MONO_FONT);
        panel.add(cronField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("解析结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 70));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("解析", "解析 Cron 表达式", e -> {
                    String cron = cronField.getText().trim();
                    if (cron.isEmpty()) return;
                    String[] parts = cron.split("\\s+");
                    if (parts.length < 6) { showError("Cron 表达式需要至少 6 个字段"); return; }
                    StringBuilder sb = new StringBuilder();
                    sb.append("秒: ").append(parts[0]).append("\n");
                    sb.append("分: ").append(parts[1]).append("\n");
                    sb.append("时: ").append(parts[2]).append("\n");
                    sb.append("日: ").append(parts[3]).append("\n");
                    sb.append("月: ").append(parts[4]).append("\n");
                    sb.append("周: ").append(parts[5]).append("\n");
                    if (parts.length > 6) sb.append("年: ").append(parts[6]);
                    output.setText(sb.toString());
                }),
                toolBtn("常用示例", "显示常用 Cron 示例", e -> {
                    output.setText("0 0 12 * * ?   每天中午12点\n"
                            + "0 0/5 * * * ?  每5分钟\n"
                            + "0 0 9-18 * * ? 每天9点到18点每小时\n"
                            + "0 0 0 * * ?    每天午夜\n"
                            + "0 0 8 * * 1-5  工作日早上8点\n"
                            + "0 0 0 1 * ?    每月1号午夜");
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== IP ====================

    private JPanel createIpPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("IP 计算");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("IP 子网计算，支持 CIDR 格式");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField ipField = new JBTextField();
        ipField.getEmptyText().setText("192.168.1.0/24");
        ipField.setFont(MONO_FONT);
        panel.add(ipField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("计算结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 80));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("计算", "计算子网信息", e -> {
                    String cidr = ipField.getText().trim();
                    try {
                        String[] parts = cidr.split("/");
                        String ip = parts[0];
                        int prefix = Integer.parseInt(parts[1]);
                        String[] octets = ip.split("\\.");
                        int ipInt = (Integer.parseInt(octets[0]) << 24) | (Integer.parseInt(octets[1]) << 16)
                                | (Integer.parseInt(octets[2]) << 8) | Integer.parseInt(octets[3]);
                        int mask = prefix == 0 ? 0 : (0xFFFFFFFF << (32 - prefix));
                        int network = ipInt & mask;
                        int broadcast = network | (~mask & 0x7FFFFFFF);
                        output.setText("IP: " + ip + "/" + prefix + "\n"
                                + "子网掩码: " + toIp(mask) + "\n"
                                + "网络地址: " + toIp(network) + "\n"
                                + "广播地址: " + toIp(broadcast) + "\n"
                                + "可用主机数: " + (int) Math.pow(2, 32 - prefix) + "\n"
                                + "可用范围: " + toIp(network + 1) + " - " + toIp(broadcast - 1));
                    } catch (Exception ex) { showError("无效的 CIDR 格式"); }
                }),
                copyBtn(output)
        ));

        return panel;
    }

    private static String toIp(int ip) {
        return (ip >>> 24) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + (ip & 0xFF);
    }

    // ==================== URL Parser ====================

    private JPanel createUrlParserPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("URL 解析");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("解析 URL 的各个组成部分");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField urlField = new JBTextField();
        urlField.getEmptyText().setText("https://user:pass@example.com:8080/path?q=1#frag");
        urlField.setFont(MONO_FONT);
        panel.add(urlField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("解析结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 80));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("解析", "解析 URL", e -> {
                    try {
                        java.net.URI uri = new java.net.URI(urlField.getText().trim());
                        StringBuilder sb = new StringBuilder();
                        sb.append("协议: ").append(uri.getScheme()).append("\n");
                        sb.append("主机: ").append(uri.getHost()).append("\n");
                        sb.append("端口: ").append(uri.getPort() > 0 ? uri.getPort() : "(默认)").append("\n");
                        sb.append("路径: ").append(uri.getPath()).append("\n");
                        sb.append("查询: ").append(uri.getQuery()).append("\n");
                        sb.append("片段: ").append(uri.getFragment()).append("\n");
                        sb.append("用户: ").append(uri.getUserInfo()).append("\n");
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("无效的 URL"); }
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Regex ====================

    private JPanel createRegexPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("正则表达式测试");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("测试正则表达式的匹配、替换和分割");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField regexField = new JBTextField();
        regexField.getEmptyText().setText("正则表达式...");
        regexField.setFont(MONO_FONT);
        panel.add(regexField);
        panel.add(Box.createVerticalStrut(6));

        JPanel flagRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        JComboBox<String> flagCombo = new JComboBox<>(new String[]{"无", "忽略大小写", "多行模式", "点号匹配换行"});
        flagRow.add(new JLabel("模式:"));
        flagRow.add(flagCombo);
        flagRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(flagRow);
        panel.add(Box.createVerticalStrut(6));

        JTextArea input = createInputArea("输入测试文本...");
        JTextArea output = createOutputArea("匹配结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("匹配", "查找所有匹配", e -> {
                    try {
                        Pattern p = compilePattern(regexField.getText(), flagCombo.getSelectedIndex());
                        java.util.regex.Matcher m = p.matcher(input.getText());
                        StringBuilder sb = new StringBuilder();
                        int count = 0;
                        while (m.find()) {
                            count++;
                            sb.append("[").append(count).append("] ").append(m.group()).append("\n");
                        }
                        if (count == 0) sb.append("无匹配");
                        else sb.insert(0, "共 " + count + " 个匹配:\n");
                        output.setText(sb.toString());
                    } catch (PatternSyntaxException ex) { showError("正则语法错误: " + ex.getMessage()); }
                }),
                toolBtn("替换", "替换匹配文本", e -> {
                    String replace = JOptionPane.showInputDialog("替换为:");
                    if (replace == null) return;
                    try {
                        Pattern p = compilePattern(regexField.getText(), flagCombo.getSelectedIndex());
                        output.setText(p.matcher(input.getText()).replaceAll(replace));
                    } catch (PatternSyntaxException ex) { showError("正则语法错误"); }
                }),
                toolBtn("分割", "按正则分割", e -> {
                    try {
                        Pattern p = compilePattern(regexField.getText(), flagCombo.getSelectedIndex());
                        String[] parts = p.split(input.getText());
                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < parts.length; i++) {
                            sb.append("[").append(i).append("] ").append(parts[i]).append("\n");
                        }
                        output.setText(sb.toString());
                    } catch (PatternSyntaxException ex) { showError("正则语法错误"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); regexField.setText(""); })
        ));

        return panel;
    }

    private static Pattern compilePattern(String regex, int flagIdx) {
        int flags = 0;
        if (flagIdx == 1) flags |= Pattern.CASE_INSENSITIVE;
        else if (flagIdx == 2) flags |= Pattern.MULTILINE;
        else if (flagIdx == 3) flags |= Pattern.DOTALL;
        return flags == 0 ? Pattern.compile(regex) : Pattern.compile(regex, flags);
    }

    // ==================== Diff ====================

    private JPanel createDiffPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("文本对比");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("对比两段文本的差异");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea leftInput = createInputArea("文本 A...");
        JTextArea rightInput = createInputArea("文本 B...");
        JPanel ioP = new JPanel(new GridLayout(1, 2, 6, 0));
        ioP.add(wrapScroll(leftInput, "文本 A"));
        ioP.add(wrapScroll(rightInput, "文本 B"));
        panel.add(ioP);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("差异结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 70));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("对比", "对比两段文本差异", e -> {
                    String a = leftInput.getText();
                    String b = rightInput.getText();
                    if (a.isEmpty() && b.isEmpty()) return;
                    String[] linesA = a.split("\n", -1);
                    String[] linesB = b.split("\n", -1);
                    int max = Math.max(linesA.length, linesB.length);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < max; i++) {
                        String la = i < linesA.length ? linesA[i] : "";
                        String lb = i < linesB.length ? linesB[i] : "";
                        if (!la.equals(lb)) {
                            sb.append("- ").append(la).append("\n");
                            sb.append("+ ").append(lb).append("\n");
                        } else {
                            sb.append("  ").append(la).append("\n");
                        }
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("交换", "交换文本 A 和 B", e -> {
                    String t = leftInput.getText();
                    leftInput.setText(rightInput.getText());
                    rightInput.setText(t);
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { leftInput.setText(""); rightInput.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Text Stats ====================

    private JPanel createTextStatsPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("文本统计");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("统计文本的字数、行数、字符数等信息");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("统计结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("统计", "统计文本信息", e -> {
                    String t = input.getText();
                    if (t.isEmpty()) { output.setText("文本为空"); return; }
                    int charCount = t.length();
                    int charNoSpace = t.replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "").length();
                    int wordCount = t.isEmpty() ? 0 : t.split("[\\s,;.?!\\u3002\\uff1b\\uff0c\\uff01\\uff1f]+").length;
                    int lineCount = t.isEmpty() ? 0 : t.split("\n", -1).length;
                    int chineseCount = 0;
                    int letterCount = 0;
                    int digitCount = 0;
                    int spaceCount = 0;
                    int punctCount = 0;
                    for (char c : t.toCharArray()) {
                        if (c >= 0x4E00 && c <= 0x9FFF) chineseCount++;
                        else if (Character.isLetter(c)) letterCount++;
                        else if (Character.isDigit(c)) digitCount++;
                        else if (Character.isWhitespace(c)) spaceCount++;
                        else punctCount++;
                    }
                    StringBuilder sb = new StringBuilder();
                    sb.append("总字符数: ").append(charCount).append("\n");
                    sb.append("非空白字符: ").append(charNoSpace).append("\n");
                    sb.append("汉字数: ").append(chineseCount).append("\n");
                    sb.append("英文字母: ").append(letterCount).append("\n");
                    sb.append("数字: ").append(digitCount).append("\n");
                    sb.append("空白字符: ").append(spaceCount).append("\n");
                    sb.append("标点符号: ").append(punctCount).append("\n");
                    sb.append("单词数: ").append(wordCount).append("\n");
                    sb.append("行数: ").append(lineCount).append("\n");
                    sb.append("段落数: ").append(t.split("\n\\s*\n", -1).length).append("\n");
                    output.setText(sb.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Case ====================

    private JPanel createCasePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("大小写转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在大小写格式之间互相转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("转换结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("大写", "全部转为大写", e -> output.setText(input.getText().toUpperCase())),
                toolBtn("小写", "全部转为小写", e -> output.setText(input.getText().toLowerCase())),
                toolBtn("首字母大写", "每个单词首字母大写", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    boolean nextUpper = true;
                    for (char c : t.toCharArray()) {
                        if (Character.isWhitespace(c)) {
                            sb.append(c);
                            nextUpper = true;
                        } else if (nextUpper) {
                            sb.append(Character.toUpperCase(c));
                            nextUpper = false;
                        } else {
                            sb.append(c);
                        }
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("驼峰转下划线", "驼峰命名转下划线", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    for (char c : t.toCharArray()) {
                        if (Character.isUpperCase(c)) {
                            sb.append('_').append(Character.toLowerCase(c));
                        } else {
                            sb.append(c);
                        }
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("下划线转驼峰", "下划线转驼峰命名", e -> {
                    String t = input.getText();
                    StringBuilder sb = new StringBuilder();
                    boolean nextUpper = false;
                    for (char c : t.toCharArray()) {
                        if (c == '_') {
                            nextUpper = true;
                        } else if (nextUpper) {
                            sb.append(Character.toUpperCase(c));
                            nextUpper = false;
                        } else {
                            sb.append(c);
                        }
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Line Ending ====================

    private JPanel createLineEndingPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("换行符转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在不同换行符格式之间转换 (CR / LF / CRLF)");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("转换结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("CRLF", "转为 Windows 换行符 \\r\\n", e -> output.setText(input.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", "\r\n"))),
                toolBtn("LF", "转为 Unix 换行符 \\n", e -> output.setText(input.getText().replace("\r\n", "\n").replace("\r", "\n"))),
                toolBtn("CR", "转为 Mac 换行符 \\r", e -> output.setText(input.getText().replace("\r\n", "\n").replace("\n", "\r"))),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== List Tools ====================

    private JPanel createListToolsPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("列表工具");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("对列表进行排序、去重、反转等操作（每行一个元素）");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入列表（每行一个元素）...");
        JTextArea output = createOutputArea("处理结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("排序", "按字母顺序排序", e -> {
                    List<String> lines = new ArrayList<>(Arrays.asList(input.getText().split("\n", -1)));
                    lines.sort(String::compareTo);
                    output.setText(String.join("\n", lines));
                }),
                toolBtn("去重", "去除重复行", e -> {
                    List<String> lines = Arrays.asList(input.getText().split("\n", -1));
                    output.setText(lines.stream().distinct().collect(Collectors.joining("\n")));
                }),
                toolBtn("反转", "反转行顺序", e -> {
                    List<String> lines = new ArrayList<>(Arrays.asList(input.getText().split("\n", -1)));
                    java.util.Collections.reverse(lines);
                    output.setText(String.join("\n", lines));
                }),
                toolBtn("去除空行", "去除所有空行", e -> {
                    List<String> lines = Arrays.asList(input.getText().split("\n", -1));
                    output.setText(lines.stream().filter(l -> !l.trim().isEmpty()).collect(Collectors.joining("\n")));
                }),
                toolBtn("添加行号", "行号", e -> {
                    String[] lines = input.getText().split("\n", -1);
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < lines.length; i++) {
                        sb.append(String.format("%4d  %s\n", i + 1, lines[i]));
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Hash ====================

    private JPanel createHashPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("哈希计算");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("计算文本的 MD5、SHA-1、SHA-256 等哈希值");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入文本...");
        JTextArea output = createOutputArea("哈希结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("MD5", "计算 MD5 哈希", e -> {
                    try {
                        MessageDigest md = MessageDigest.getInstance("MD5");
                        byte[] digest = md.digest(input.getText().getBytes(StandardCharsets.UTF_8));
                        StringBuilder sb = new StringBuilder();
                        for (byte b : digest) sb.append(String.format("%02x", b));
                        output.setText("MD5:  " + sb.toString().toUpperCase() + "\nmd5:  " + sb);
                    } catch (Exception ex) { showError("计算失败"); }
                }),
                toolBtn("SHA-1", "计算 SHA-1 哈希", e -> {
                    try {
                        MessageDigest md = MessageDigest.getInstance("SHA-1");
                        byte[] digest = md.digest(input.getText().getBytes(StandardCharsets.UTF_8));
                        StringBuilder sb = new StringBuilder();
                        for (byte b : digest) sb.append(String.format("%02x", b));
                        output.setText("SHA-1:  " + sb.toString().toUpperCase() + "\nsha-1:  " + sb);
                    } catch (Exception ex) { showError("计算失败"); }
                }),
                toolBtn("SHA-256", "计算 SHA-256 哈希", e -> {
                    try {
                        MessageDigest md = MessageDigest.getInstance("SHA-256");
                        byte[] digest = md.digest(input.getText().getBytes(StandardCharsets.UTF_8));
                        StringBuilder sb = new StringBuilder();
                        for (byte b : digest) sb.append(String.format("%02x", b));
                        output.setText("SHA-256:  " + sb.toString().toUpperCase() + "\nsha-256:  " + sb);
                    } catch (Exception ex) { showError("计算失败"); }
                }),
                toolBtn("SHA-512", "计算 SHA-512 哈希", e -> {
                    try {
                        MessageDigest md = MessageDigest.getInstance("SHA-512");
                        byte[] digest = md.digest(input.getText().getBytes(StandardCharsets.UTF_8));
                        StringBuilder sb = new StringBuilder();
                        for (byte b : digest) sb.append(String.format("%02x", b));
                        output.setText("SHA-512:  " + sb.toString().toUpperCase() + "\nsha-512:  " + sb);
                    } catch (Exception ex) { showError("计算失败"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Base Convert ====================

    private JPanel createBaseConvertPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("进制转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在二进制、八进制、十进制、十六进制之间转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField input = new JBTextField();
        input.getEmptyText().setText("输入数值...");
        input.setFont(MONO_FONT);
        panel.add(input);
        panel.add(Box.createVerticalStrut(6));

        JPanel radioRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        JComboBox<String> fromBase = new JComboBox<>(new String[]{"二进制", "八进制", "十进制", "十六进制"});
        JComboBox<String> toBase = new JComboBox<>(new String[]{"十进制", "二进制", "八进制", "十六进制"});
        radioRow.add(new JLabel("从:"));
        radioRow.add(fromBase);
        radioRow.add(new JLabel("  到:"));
        radioRow.add(toBase);
        radioRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(radioRow);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 70));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转换", "进制转换", e -> {
                    try {
                        String text = input.getText().trim();
                        if (text.isEmpty()) return;
                        int from = fromBase.getSelectedIndex();
                        int radix = from == 0 ? 2 : from == 1 ? 8 : from == 2 ? 10 : 16;
                        long value = Long.parseLong(text.replaceAll("[^0-9a-fA-F]", ""), radix);
                        int to = toBase.getSelectedIndex();
                        String result;
                        switch (to) {
                            case 0: result = Long.toBinaryString(value); break;
                            case 1: result = Long.toOctalString(value); break;
                            case 2: result = Long.toString(value); break;
                            case 3: result = Long.toHexString(value).toUpperCase(); break;
                            default: result = Long.toString(value);
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.append("十进制: ").append(value).append("\n");
                        sb.append("二进制: ").append(Long.toBinaryString(value)).append("\n");
                        sb.append("八进制: ").append(Long.toOctalString(value)).append("\n");
                        sb.append("十六进制: ").append(Long.toHexString(value).toUpperCase()).append("\n");
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("无效的数值"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Color ====================

    private JPanel createColorPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("颜色转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在 HEX、RGB、HSL 等颜色格式之间转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField input = new JBTextField();
        input.getEmptyText().setText("#FF0000 或 rgb(255,0,0)");
        input.setFont(MONO_FONT);
        panel.add(input);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 80));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        JPanel previewPanel = new JPanel();
        previewPanel.setPreferredSize(new Dimension(100, 40));
        previewPanel.setBorder(BorderFactory.createLineBorder(JBColor.border()));
        previewPanel.setBackground(Color.WHITE);
        panel.add(previewPanel);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转换", "颜色格式转换", e -> {
                    try {
                        String text = input.getText().trim();
                        Color c = null;
                        if (text.startsWith("#")) {
                            c = Color.decode(text);
                        } else if (text.startsWith("rgb")) {
                            String[] parts = text.replaceAll("[^0-9,]", "").split(",");
                            c = new Color(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
                        }
                        if (c == null) { showError("不支持的颜色格式"); return; }
                        previewPanel.setBackground(c);
                        StringBuilder sb = new StringBuilder();
                        sb.append("HEX: #").append(String.format("%02X%02X%02X", c.getRed(), c.getGreen(), c.getBlue())).append("\n");
                        sb.append("RGB: rgb(").append(c.getRed()).append(", ").append(c.getGreen()).append(", ").append(c.getBlue()).append(")\n");
                        float[] hsb = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
                        sb.append("HSL: hsl(").append(Math.round(hsb[0] * 360)).append(", ").append(Math.round(hsb[1] * 100)).append("%, ").append(Math.round(hsb[2] * 100)).append("%)\n");
                        sb.append("CMYK: cmyk(").append(String.format("%.1f%%", (1 - c.getRed()/255.0) * 100)).append(", ")
                                .append(String.format("%.1f%%", (1 - c.getGreen()/255.0) * 100)).append(", ")
                                .append(String.format("%.1f%%", (1 - c.getBlue()/255.0) * 100)).append(", 0%)\n");
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("颜色格式无效"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); previewPanel.setBackground(Color.WHITE); })
        ));

        return panel;
    }

    // ==================== JWT ====================

    private JPanel createJwtPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("JWT 解码");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("解码 JWT Token，查看 Header 和 Payload");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 JWT Token...");
        JTextArea output = createOutputArea("解码结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("解码", "解码 JWT", e -> {
                    String t = input.getText().trim();
                    if (t.isEmpty()) return;
                    String[] parts = t.split("\\.");
                    if (parts.length < 2) { showError("无效的 JWT 格式"); return; }
                    try {
                        StringBuilder sb = new StringBuilder();
                        sb.append("=== Header ===\n");
                        sb.append(new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8));
                        sb.append("\n\n=== Payload ===\n");
                        sb.append(new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8));
                        if (parts.length > 2) {
                            sb.append("\n\n=== Signature ===\n");
                            sb.append(parts[2]);
                        }
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("JWT 解码失败"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Endian ====================

    private JPanel createEndianPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("大小端转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在 Big Endian 和 Little Endian 之间转换十六进制数据");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField input = new JBTextField();
        input.getEmptyText().setText("输入十六进制，如: 0A1B2C3D");
        input.setFont(MONO_FONT);
        panel.add(input);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 70));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("BE -> LE", "Big Endian 转 Little Endian", e -> {
                    String hex = input.getText().trim().replaceAll("[^0-9a-fA-F]", "");
                    if (hex.length() % 2 != 0) { showError("十六进制长度必须为偶数"); return; }
                    StringBuilder sb = new StringBuilder();
                    for (int i = hex.length() - 2; i >= 0; i -= 2) {
                        sb.append(hex, i, i + 2);
                    }
                    output.setText("原始 (BE): " + hex.toUpperCase() + "\n转换 (LE): " + sb.toString().toUpperCase());
                }),
                toolBtn("LE -> BE", "Little Endian 转 Big Endian", e -> {
                    String hex = input.getText().trim().replaceAll("[^0-9a-fA-F]", "");
                    if (hex.length() % 2 != 0) { showError("十六进制长度必须为偶数"); return; }
                    StringBuilder sb = new StringBuilder();
                    for (int i = hex.length() - 2; i >= 0; i -= 2) {
                        sb.append(hex, i, i + 2);
                    }
                    output.setText("原始 (LE): " + hex.toUpperCase() + "\n转换 (BE): " + sb.toString().toUpperCase());
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== File Size ====================

    private JPanel createFileSizePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("文件大小转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在 B / KB / MB / GB / TB 之间转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField input = new JBTextField();
        input.getEmptyText().setText("1024");
        input.setFont(MONO_FONT);
        panel.add(input);
        panel.add(Box.createVerticalStrut(6));

        JPanel unitRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
        JComboBox<String> unitCombo = new JComboBox<>(new String[]{"B", "KB", "MB", "GB", "TB"});
        unitRow.add(new JLabel("单位:"));
        unitRow.add(unitCombo);
        unitRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32));
        panel.add(unitRow);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 70));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("转换", "文件大小转换", e -> {
                    try {
                        double value = Double.parseDouble(input.getText().trim());
                        int unit = unitCombo.getSelectedIndex();
                        double bytes = value * Math.pow(1024, unit);
                        StringBuilder sb = new StringBuilder();
                        sb.append("B:   ").append(String.format("%.0f", bytes)).append("\n");
                        sb.append("KB:  ").append(String.format("%.2f", bytes / 1024)).append("\n");
                        sb.append("MB:  ").append(String.format("%.2f", bytes / (1024*1024))).append("\n");
                        sb.append("GB:  ").append(String.format("%.2f", bytes / (1024*1024*1024))).append("\n");
                        sb.append("TB:  ").append(String.format("%.4f", bytes / (1024.0*1024*1024*1024))).append("\n");
                        output.setText(sb.toString());
                    } catch (NumberFormatException ex) { showError("请输入有效的数字"); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== Chmod ====================

    private JPanel createChmodPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("Unix 权限计算");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在数字权限和符号权限之间转换 (chmod)");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField input = new JBTextField();
        input.getEmptyText().setText("755 或 u+rwx,g+rx,o+rx");
        input.setFont(MONO_FONT);
        panel.add(input);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("转换结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 80));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("数字转符号", "数字权限转符号表示", e -> {
                    String t = input.getText().trim();
                    try {
                        int perm = Integer.parseInt(t, 8);
                        StringBuilder sb = new StringBuilder();
                        sb.append("数字: ").append(t).append("\n");
                        sb.append("符号: ").append(permToSymbol(perm)).append("\n");
                        sb.append("所有者: ").append(t.charAt(0) == '7' ? "读+写+执行" : t.charAt(0) == '6' ? "读+写" : t.charAt(0) == '5' ? "读+执行" : t.charAt(0) == '4' ? "只读" : "无权限").append("\n");
                        sb.append("用户组: ").append(t.charAt(1) == '7' ? "读+写+执行" : t.charAt(1) == '6' ? "读+写" : t.charAt(1) == '5' ? "读+执行" : t.charAt(1) == '4' ? "只读" : "无权限").append("\n");
                        sb.append("其他: ").append(t.charAt(2) == '7' ? "读+写+执行" : t.charAt(2) == '6' ? "读+写" : t.charAt(2) == '5' ? "读+执行" : t.charAt(2) == '4' ? "只读" : "无权限").append("\n");
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("无效的数字权限"); }
                }),
                toolBtn("符号转数字", "符号权限转数字表示", e -> {
                    String t = input.getText().trim().toLowerCase();
                    int perm = 0;
                    if (t.contains("u")) {
                        if (t.contains("r")) perm |= 0x100;
                        if (t.contains("w")) perm |= 0x080;
                        if (t.contains("x")) perm |= 0x040;
                    }
                    if (t.contains("g")) {
                        if (t.contains("r")) perm |= 0x020;
                        if (t.contains("w")) perm |= 0x010;
                        if (t.contains("x")) perm |= 0x008;
                    }
                    if (t.contains("o")) {
                        if (t.contains("r")) perm |= 0x004;
                        if (t.contains("w")) perm |= 0x002;
                        if (t.contains("x")) perm |= 0x001;
                    }
                    output.setText("符号: " + t + "\n数字: " + Integer.toOctalString(perm));
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    private static String permToSymbol(int perm) {
        StringBuilder sb = new StringBuilder();
        sb.append((perm & 0x100) != 0 ? 'r' : '-');
        sb.append((perm & 0x080) != 0 ? 'w' : '-');
        sb.append((perm & 0x040) != 0 ? 'x' : '-');
        sb.append((perm & 0x020) != 0 ? 'r' : '-');
        sb.append((perm & 0x010) != 0 ? 'w' : '-');
        sb.append((perm & 0x008) != 0 ? 'x' : '-');
        sb.append((perm & 0x004) != 0 ? 'r' : '-');
        sb.append((perm & 0x002) != 0 ? 'w' : '-');
        sb.append((perm & 0x001) != 0 ? 'x' : '-');
        return sb.toString();
    }

    // ==================== CSV/JSON ====================

    private JPanel createCsvJsonPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("CSV / JSON 转换");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("在 CSV 和 JSON 格式之间互相转换");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入 CSV 或 JSON...");
        JTextArea output = createOutputArea("转换结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("CSV -> JSON", "CSV 转 JSON", e -> {
                    String t = input.getText().trim();
                    if (t.isEmpty()) return;
                    String[] lines = t.split("\n");
                    if (lines.length < 2) { showError("CSV 需要至少标题行和一行数据"); return; }
                    String[] headers = lines[0].split(",");
                    for (int i = 0; i < headers.length; i++) headers[i] = headers[i].trim();
                    StringBuilder sb = new StringBuilder("[\n");
                    for (int i = 1; i < lines.length; i++) {
                        String[] values = lines[i].split(",");
                        sb.append("  {");
                        for (int j = 0; j < headers.length && j < values.length; j++) {
                            if (j > 0) sb.append(", ");
                            sb.append("\"").append(headers[j]).append("\": \"").append(values[j].trim()).append("\"");
                        }
                        sb.append("}");
                        if (i < lines.length - 1) sb.append(",");
                        sb.append("\n");
                    }
                    sb.append("]");
                    output.setText(sb.toString());
                }),
                toolBtn("JSON -> CSV", "JSON 转 CSV", e -> {
                    String t = input.getText().trim();
                    try {
                        com.google.gson.JsonArray arr = com.google.gson.JsonParser.parseString(t).getAsJsonArray();
                        if (arr.size() == 0) return;
                        java.util.Set<String> keys = new java.util.LinkedHashSet<>();
                        for (int i = 0; i < arr.size(); i++) {
                            com.google.gson.JsonObject obj = arr.get(i).getAsJsonObject();
                            keys.addAll(obj.keySet());
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.append(String.join(",", keys)).append("\n");
                        for (int i = 0; i < arr.size(); i++) {
                            com.google.gson.JsonObject obj = arr.get(i).getAsJsonObject();
                            List<String> row = new ArrayList<>();
                            for (String key : keys) {
                                com.google.gson.JsonElement el = obj.get(key);
                                row.add(el != null ? el.getAsString() : "");
                            }
                            sb.append(String.join(",", row)).append("\n");
                        }
                        output.setText(sb.toString());
                    } catch (Exception ex) { showError("JSON 解析失败: " + ex.getMessage()); }
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }

    // ==================== ASCII ====================

    private JPanel createAsciiPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("ASCII 码表");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("查看 ASCII 字符编码对照表");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea output = createOutputArea("ASCII 码表...");
        output.setEditable(false);
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 300));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("显示码表", "显示 ASCII 码表", e -> {
                    StringBuilder sb = new StringBuilder();
                    sb.append("Dec  Hex  Char\n");
                    sb.append("---  ---  ----\n");
                    for (int i = 0; i < 128; i++) {
                        String ch = i < 32 ? "控制字符" : i == 32 ? "空格" : i == 127 ? "DEL" : String.valueOf((char) i);
                        sb.append(String.format("%3d  %3s  %s\n", i, Integer.toHexString(i).toUpperCase(), ch));
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("字符转 ASCII", "输入字符查看 ASCII 码", e -> {
                    String t = javax.swing.JOptionPane.showInputDialog("输入字符:");
                    if (t != null && !t.isEmpty()) {
                        StringBuilder sb = new StringBuilder();
                        for (char c : t.toCharArray()) {
                            sb.append("'").append(c).append("' -> Dec: ").append((int) c)
                                    .append("  Hex: ").append(Integer.toHexString(c).toUpperCase()).append("\n");
                        }
                        output.setText(sb.toString());
                    }
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== HTTP Status ====================

    private JPanel createHttpStatusPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("HTTP 状态码");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("查询 HTTP 状态码的含义");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField codeField = new JBTextField();
        codeField.getEmptyText().setText("输入状态码，如 404");
        codeField.setFont(MONO_FONT);
        panel.add(codeField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("查询结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 200));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        Map<String, String> statusMap = new LinkedHashMap<>();
        statusMap.put("100", "Continue - 继续");
        statusMap.put("101", "Switching Protocols - 切换协议");
        statusMap.put("200", "OK - 请求成功");
        statusMap.put("201", "Created - 已创建");
        statusMap.put("204", "No Content - 无内容");
        statusMap.put("301", "Moved Permanently - 永久重定向");
        statusMap.put("302", "Found - 临时重定向");
        statusMap.put("304", "Not Modified - 未修改");
        statusMap.put("400", "Bad Request - 错误请求");
        statusMap.put("401", "Unauthorized - 未授权");
        statusMap.put("403", "Forbidden - 禁止访问");
        statusMap.put("404", "Not Found - 未找到");
        statusMap.put("405", "Method Not Allowed - 方法不允许");
        statusMap.put("408", "Request Timeout - 请求超时");
        statusMap.put("429", "Too Many Requests - 请求过多");
        statusMap.put("500", "Internal Server Error - 服务器内部错误");
        statusMap.put("502", "Bad Gateway - 网关错误");
        statusMap.put("503", "Service Unavailable - 服务不可用");
        statusMap.put("504", "Gateway Timeout - 网关超时");

        panel.add(btnRow(
                toolBtn("查询", "查询状态码", e -> {
                    String code = codeField.getText().trim();
                    String descText = statusMap.get(code);
                    if (descText != null) {
                        output.setText(code + " - " + descText);
                    } else {
                        output.setText("未知状态码: " + code);
                    }
                }),
                toolBtn("全部显示", "显示所有常见状态码", e -> {
                    StringBuilder sb = new StringBuilder();
                    for (Map.Entry<String, String> entry : statusMap.entrySet()) {
                        sb.append(entry.getKey()).append(" - ").append(entry.getValue()).append("\n");
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== MIME ====================

    private JPanel createMimePanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("MIME 类型查询");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("查询常见文件扩展名的 MIME 类型");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JBTextField extField = new JBTextField();
        extField.getEmptyText().setText("输入扩展名，如 .html");
        extField.setFont(MONO_FONT);
        panel.add(extField);
        panel.add(Box.createVerticalStrut(8));

        JTextArea output = createOutputArea("查询结果...");
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 200));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        Map<String, String> mimeMap = new LinkedHashMap<>();
        mimeMap.put(".html", "text/html");
        mimeMap.put(".css", "text/css");
        mimeMap.put(".js", "application/javascript");
        mimeMap.put(".json", "application/json");
        mimeMap.put(".xml", "application/xml");
        mimeMap.put(".png", "image/png");
        mimeMap.put(".jpg", "image/jpeg");
        mimeMap.put(".gif", "image/gif");
        mimeMap.put(".svg", "image/svg+xml");
        mimeMap.put(".ico", "image/x-icon");
        mimeMap.put(".pdf", "application/pdf");
        mimeMap.put(".zip", "application/zip");
        mimeMap.put(".tar", "application/x-tar");
        mimeMap.put(".mp3", "audio/mpeg");
        mimeMap.put(".mp4", "video/mp4");
        mimeMap.put(".txt", "text/plain");
        mimeMap.put(".csv", "text/csv");
        mimeMap.put(".yaml", "application/x-yaml");
        mimeMap.put(".yml", "application/x-yaml");
        mimeMap.put(".md", "text/markdown");

        panel.add(btnRow(
                toolBtn("查询", "查询 MIME 类型", e -> {
                    String ext = extField.getText().trim().toLowerCase();
                    if (!ext.startsWith(".")) ext = "." + ext;
                    String mime = mimeMap.get(ext);
                    if (mime != null) {
                        output.setText(ext + " -> " + mime);
                    } else {
                        output.setText("未知扩展名: " + ext);
                    }
                }),
                toolBtn("全部显示", "显示所有 MIME 类型", e -> {
                    StringBuilder sb = new StringBuilder();
                    for (Map.Entry<String, String> entry : mimeMap.entrySet()) {
                        sb.append(entry.getKey()).append(" -> ").append(entry.getValue()).append("\n");
                    }
                    output.setText(sb.toString());
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Regex Cheat ====================

    private JPanel createRegexCheatPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("正则表达式速查");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("常用正则表达式语法和示例");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea output = createOutputArea("正则速查...");
        output.setEditable(false);
        JScrollPane sp = new JBScrollPane(output);
        sp.setPreferredSize(new Dimension(200, 300));
        panel.add(sp);
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("基础语法", "显示基础语法", e -> {
                    output.setText("=== 基础语法 ===\n"
                            + ".       任意字符\n"
                            + "\\d      数字 [0-9]\n"
                            + "\\D      非数字\n"
                            + "\\w      单词字符 [a-zA-Z0-9_]\n"
                            + "\\W      非单词字符\n"
                            + "\\s      空白字符\n"
                            + "\\S      非空白字符\n"
                            + "\\b      单词边界\n"
                            + "\\B      非单词边界\n"
                            + "^       行首\n"
                            + "$       行尾");
                }),
                toolBtn("量词", "显示量词语法", e -> {
                    output.setText("=== 量词 ===\n"
                            + "*       零次或多次\n"
                            + "+       一次或多次\n"
                            + "?       零次或一次\n"
                            + "{n}     恰好 n 次\n"
                            + "{n,}    至少 n 次\n"
                            + "{n,m}   n 到 m 次\n"
                            + "*?      非贪婪匹配\n"
                            + "+?      非贪婪匹配\n"
                            + "??      非贪婪匹配");
                }),
                toolBtn("常用模式", "显示常用正则模式", e -> {
                    output.setText("=== 常用模式 ===\n"
                            + "邮箱: ^[\\w.-]+@[\\w.-]+\\.\\w{2,}$\n"
                            + "手机号: ^1[3-9]\\d{9}$\n"
                            + "URL: ^https?://[\\w./?-]+$\n"
                            + "IP: ^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\n"
                            + "日期: ^\\d{4}-\\d{2}-\\d{2}$\n"
                            + "时间: ^\\d{2}:\\d{2}:\\d{2}$\n"
                            + "数字: ^\\d+$\n"
                            + "中文: ^[\\u4e00-\\u9fff]+$");
                }),
                copyBtn(output)
        ));

        return panel;
    }

    // ==================== Password Strength ====================

    private JPanel createPasswordPanel() {
        JPanel panel = paddedPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel title = new JLabel("密码强度检测");
        title.setFont(TITLE_FONT);
        panel.add(title);
        panel.add(Box.createVerticalStrut(4));
        JLabel desc = new JLabel("检测密码强度并给出改进建议");
        desc.setForeground(SUBTITLE);
        panel.add(desc);
        panel.add(Box.createVerticalStrut(12));

        JTextArea input = createInputArea("输入密码...");
        JTextArea output = createOutputArea("检测结果...");
        panel.add(ioPanel(input, output));
        panel.add(Box.createVerticalStrut(8));

        panel.add(btnRow(
                toolBtn("检测", "检测密码强度", e -> {
                    String pwd = input.getText();
                    if (pwd.isEmpty()) { output.setText("请输入密码"); return; }
                    int score = 0;
                    StringBuilder feedback = new StringBuilder();

                    if (pwd.length() >= 8) { score += 20; } else { feedback.append("- 密码长度至少 8 位\n"); }
                    if (pwd.length() >= 12) { score += 10; }
                    if (pwd.matches(".*[a-z].*")) { score += 15; } else { feedback.append("- 缺少小写字母\n"); }
                    if (pwd.matches(".*[A-Z].*")) { score += 15; } else { feedback.append("- 缺少大写字母\n"); }
                    if (pwd.matches(".*\\d.*")) { score += 15; } else { feedback.append("- 缺少数字\n"); }
                    if (pwd.matches(".*[^a-zA-Z0-9].*")) { score += 20; } else { feedback.append("- 缺少特殊字符\n"); }

                    String strength;
                    if (score >= 80) { strength = "非常强"; }
                    else if (score >= 60) { strength = "强"; }
                    else if (score >= 40) { strength = "中等"; }
                    else if (score >= 20) { strength = "弱"; }
                    else { strength = "非常弱"; }

                    StringBuilder sb = new StringBuilder();
                    sb.append("密码强度: ").append(strength).append(" (").append(score).append("/100)\n");
                    sb.append("密码长度: ").append(pwd.length()).append("\n");
                    sb.append("包含小写: ").append(pwd.matches(".*[a-z].*") ? "是" : "否").append("\n");
                    sb.append("包含大写: ").append(pwd.matches(".*[A-Z].*") ? "是" : "否").append("\n");
                    sb.append("包含数字: ").append(pwd.matches(".*\\d.*") ? "是" : "否").append("\n");
                    sb.append("包含特殊: ").append(pwd.matches(".*[^a-zA-Z0-9].*") ? "是" : "否").append("\n");
                    if (feedback.length() > 0) {
                        sb.append("\n改进建议:\n").append(feedback);
                    }
                    output.setText(sb.toString());
                }),
                toolBtn("生成强密码", "生成随机强密码", e -> {
                    String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
                    String lower = "abcdefghijklmnopqrstuvwxyz";
                    String digits = "0123456789";
                    String special = "!@#$%^&*()_+-=[]{}|;:,.<>?";
                    String all = upper + lower + digits + special;
                    StringBuilder sb = new StringBuilder();
                    sb.append(upper.charAt(SECURE_RANDOM.nextInt(upper.length())));
                    sb.append(lower.charAt(SECURE_RANDOM.nextInt(lower.length())));
                    sb.append(digits.charAt(SECURE_RANDOM.nextInt(digits.length())));
                    sb.append(special.charAt(SECURE_RANDOM.nextInt(special.length())));
                    for (int i = 0; i < 12; i++) {
                        sb.append(all.charAt(SECURE_RANDOM.nextInt(all.length())));
                    }
                    String pwd = sb.toString();
                    input.setText(pwd);
                    output.setText("已生成强密码，长度: " + pwd.length() + " 位");
                }),
                copyBtn(output),
                toolBtn("清空", "清空所有", e -> { input.setText(""); output.setText(""); })
        ));

        return panel;
    }
}