package com.huangzj.protoplugin.memo;

import com.intellij.openapi.project.Project;
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 com.intellij.util.ui.UIUtil;
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.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
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.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.jetbrains.annotations.NotNull;

public final class MemoPanel extends JPanel {

    private final Project project;
    private final DefaultListModel<MemoItem> listModel = new DefaultListModel<>();
    private final JBList<MemoItem> list = new JBList<>(listModel);
    private final JBTextField titleField = new JBTextField();
    private final JBTextArea contentArea = new JBTextArea();
    private final JBTextField tagField = new JBTextField();
    private final JButton saveBtn = createStyledBtn("保存", new Color(0x27AE60));
    private final JButton newBtn = createStyledBtn("新建", new Color(0x2980B9));
    private final JButton deleteBtn = createStyledBtn("删除", new Color(0xE74C3C));
    private final JButton pinBtn = createStyledBtn("置顶", new Color(0xE67E22));
    private final JLabel themeChooserLabel = new JLabel("主题");
    private final JComboBox<MemoUiTheme> themeCombo = new JComboBox<>(MemoUiTheme.values());
    private final JLabel titleLabel = new JLabel("标题");
    private final JLabel contentLabel = new JLabel("内容");
    private final JLabel tagLabel = new JLabel("标签");
    private final JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 4));
    private final JPanel topStack = new JPanel(new BorderLayout());
    private JSplitPane split;
    private JBScrollPane listScroll;
    private JBScrollPane contentScroll;
    private final JPanel titleBlock = new JPanel(new BorderLayout(4, 4));
    private final JPanel contentBlock = new JPanel(new BorderLayout(4, 4));
    private final JPanel tagBlock = new JPanel(new BorderLayout(4, 4));
    private final JPanel right = new JPanel(new BorderLayout(8, 8));
    private final JBTextField searchField = new JBTextField();
    private final JLabel countLabel = new JLabel(" ");
    private final JLabel modifiedLabel = new JLabel(" ");
    private final JLabel wordCountLabel = new JLabel(" ");
    private final JPanel leftPanel = new JPanel(new BorderLayout(0, 4));
    private final JPanel searchPanel = new JPanel(new BorderLayout(4, 0));
    private final JLabel searchIcon = new JLabel("\uD83D\uDD0D");
    private final JPanel editorStatusBar = new JPanel(new BorderLayout());
    private final JComboBox<String> sortCombo = new JComboBox<>(new String[]{"按更新时间", "按标题", "置顶优先"});
    private final JButton exportBtn = createStyledBtn("导出", new Color(0x8E44AD));
    private final JButton importBtn = createStyledBtn("导入", new Color(0x2C3E50));
    private final JPanel tagDisplayPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 2)) {{
        setOpaque(false);
    }};

    private boolean loadingSelection;
    private boolean themeComboSuppress;
    private MemoUiTheme currentTheme = MemoUiTheme.IDE_DEFAULT;
    private final Font defaultLabelFont;
    private List<MemoItem> allItems = new ArrayList<>();
    private MemoItem currentEditingItem;

    public MemoPanel(@NotNull Project project) {
        super(new BorderLayout(8, 8));
        this.project = project;
        defaultLabelFont = titleLabel.getFont();
        setBorder(JBUI.Borders.empty(8));

        initToolbar();
        initSearchPanel();
        initList();
        initLeftPanel();
        initEditor();
        initSplit();
        initTheme();
        initListeners();

        reloadListModel();
    }

    private static JButton createStyledBtn(String text, Color bg) {
        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));
        return b;
    }

    private void initToolbar() {
        toolbar.setOpaque(false);
        newBtn.setToolTipText("新建一条备忘录");
        saveBtn.setToolTipText("保存当前备忘录");
        deleteBtn.setToolTipText("删除选中的备忘录");
        pinBtn.setToolTipText("切换置顶状态");
        toolbar.add(newBtn);
        toolbar.add(saveBtn);
        toolbar.add(pinBtn);
        toolbar.add(deleteBtn);

        JLabel sep = new JLabel("\u2502");
        sep.setForeground(new JBColor(new Color(0xCCCCCC), new Color(0x555555)));
        sep.setBorder(JBUI.Borders.empty(0, 6));
        toolbar.add(sep);

        toolbar.add(new JLabel("\uD83D\uDD0D"));
        toolbar.add(searchField);
        searchField.setPreferredSize(new Dimension(140, 26));
        searchField.getEmptyText().setText("搜索...");

        toolbar.add(new JLabel("排序:"));
        toolbar.add(sortCombo);
        sortCombo.setPreferredSize(new Dimension(100, 26));

        toolbar.add(sep);

        toolbar.add(exportBtn);
        toolbar.add(importBtn);

        toolbar.add(new JLabel("主题:"));
        toolbar.add(themeCombo);
        themeCombo.setPreferredSize(new Dimension(120, 26));

        JBScrollPane scrollToolbar = new JBScrollPane(toolbar,
                JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        scrollToolbar.setBorder(null);
        scrollToolbar.getHorizontalScrollBar().setPreferredSize(new Dimension(0, 6));

        JPanel toolbarWrapper = new JPanel(new BorderLayout());
        toolbarWrapper.setOpaque(true);
        toolbarWrapper.setBackground(new JBColor(new Color(0xF5F5F5), new Color(0x2D2D2D)));
        toolbarWrapper.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(new JBColor(new Color(0xE0E0E0), new Color(0x3A3A3A)), 0, 0, 1, 0),
                JBUI.Borders.empty(2, 4)));
        toolbarWrapper.add(scrollToolbar, BorderLayout.CENTER);

        topStack.setOpaque(false);
        topStack.add(toolbarWrapper, BorderLayout.CENTER);
        add(topStack, BorderLayout.NORTH);
    }

    private void initSearchPanel() {
        searchPanel.setOpaque(false);
        searchPanel.add(searchIcon, BorderLayout.WEST);
        searchPanel.add(searchField, BorderLayout.CENTER);
        searchPanel.setBorder(JBUI.Borders.empty(0, 0, 6, 0));
        searchField.getEmptyText().setText("搜索备忘录...");
        searchField.setBackground(new JBColor(new Color(0xFFFFFF), new Color(0x3C3C3C)));
    }

    private void initList() {
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        installListRenderer();
        listScroll = new JBScrollPane(list);
        listScroll.setPreferredSize(new Dimension(240, 300));
        listScroll.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(new JBColor(new Color(0xDDDDDD), new Color(0x444444)), 1),
                JBUI.Borders.empty(0)));

        countLabel.setFont(countLabel.getFont().deriveFont(Font.PLAIN, 11f));
        countLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        countLabel.setBorder(JBUI.Borders.empty(4, 4, 0, 4));
    }

    private void initLeftPanel() {
        leftPanel.setOpaque(true);
        leftPanel.setBackground(new JBColor(new Color(0xFAFAFA), new Color(0x252525)));
        leftPanel.add(searchPanel, BorderLayout.NORTH);
        leftPanel.add(listScroll, BorderLayout.CENTER);
        leftPanel.add(countLabel, BorderLayout.SOUTH);
        leftPanel.setBorder(JBUI.Borders.empty(0, 0, 0, 4));
    }

    private void initEditor() {
        contentArea.setLineWrap(true);
        contentArea.setWrapStyleWord(true);
        contentScroll = new JBScrollPane(contentArea);
        contentScroll.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(new JBColor(new Color(0xDDDDDD), new Color(0x444444)), 1),
                JBUI.Borders.empty(0)));

        titleBlock.setOpaque(false);
        titleBlock.add(titleLabel, BorderLayout.NORTH);
        titleBlock.add(titleField, BorderLayout.CENTER);

        tagBlock.setOpaque(false);
        JPanel tagInner = new JPanel(new BorderLayout(4, 2));
        tagInner.setOpaque(false);
        tagInner.add(tagField, BorderLayout.CENTER);
        tagInner.add(tagDisplayPanel, BorderLayout.SOUTH);
        tagBlock.add(tagLabel, BorderLayout.NORTH);
        tagBlock.add(tagInner, BorderLayout.CENTER);

        contentBlock.setOpaque(false);
        contentBlock.add(contentLabel, BorderLayout.NORTH);
        contentBlock.add(contentScroll, BorderLayout.CENTER);

        modifiedLabel.setFont(modifiedLabel.getFont().deriveFont(Font.PLAIN, 11f));
        modifiedLabel.setBorder(JBUI.Borders.empty(2, 0, 0, 0));
        wordCountLabel.setFont(wordCountLabel.getFont().deriveFont(Font.PLAIN, 11f));
        wordCountLabel.setBorder(JBUI.Borders.empty(2, 0, 0, 8));
        editorStatusBar.setOpaque(false);
        editorStatusBar.add(wordCountLabel, BorderLayout.WEST);
        editorStatusBar.add(modifiedLabel, BorderLayout.EAST);

        right.setOpaque(false);
        right.add(titleBlock, BorderLayout.NORTH);
        right.add(contentBlock, BorderLayout.CENTER);
        right.add(editorStatusBar, BorderLayout.SOUTH);
        titleField.setPreferredSize(new Dimension(200, 32));
        tagField.setPreferredSize(new Dimension(200, 28));
        tagField.getEmptyText().setText("输入标签后按回车...");

        titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 12f));
        contentLabel.setFont(contentLabel.getFont().deriveFont(Font.BOLD, 12f));
        tagLabel.setFont(tagLabel.getFont().deriveFont(Font.BOLD, 12f));
    }

    private void initSplit() {
        split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, right);
        split.setResizeWeight(0.28);
        split.setDividerSize(4);
        split.setOpaque(false);
        split.setBorder(null);
        add(split, BorderLayout.CENTER);
    }

    private void initTheme() {
        MemoUiTheme initial = MemoProjectService.getInstance(project).getMemoTheme();
        themeComboSuppress = true;
        themeCombo.setSelectedItem(initial);
        themeComboSuppress = false;
        applyTheme(initial);
    }

    private void initListeners() {
        themeCombo.addActionListener(e -> {
            if (themeComboSuppress) return;
            MemoUiTheme t = (MemoUiTheme) themeCombo.getSelectedItem();
            if (t != null) {
                MemoProjectService.getInstance(project).setMemoTheme(t);
                applyTheme(t);
            }
        });

        list.addListSelectionListener((ListSelectionEvent e) -> {
            if (e.getValueIsAdjusting()) return;
            loadSelectedIntoEditor();
        });

        newBtn.addActionListener(e -> createMemo());
        saveBtn.addActionListener(e -> saveCurrent());
        deleteBtn.addActionListener(e -> deleteCurrent());
        pinBtn.addActionListener(e -> togglePin());
        exportBtn.addActionListener(e -> exportMemos());
        importBtn.addActionListener(e -> importMemos());
        sortCombo.addActionListener(e -> applySortAndFilter());

        tagField.addActionListener(e -> addTagFromField());

        titleField.getDocument().addDocumentListener(new DocumentListener() {
            @Override public void insertUpdate(DocumentEvent e) { refreshSelectedTitleInList(); updateWordCount(); }
            @Override public void removeUpdate(DocumentEvent e) { refreshSelectedTitleInList(); updateWordCount(); }
            @Override public void changedUpdate(DocumentEvent e) { refreshSelectedTitleInList(); updateWordCount(); }
        });

        contentArea.getDocument().addDocumentListener(new DocumentListener() {
            @Override public void insertUpdate(DocumentEvent e) { markModified(); updateWordCount(); }
            @Override public void removeUpdate(DocumentEvent e) { markModified(); updateWordCount(); }
            @Override public void changedUpdate(DocumentEvent e) { markModified(); updateWordCount(); }
        });

        searchField.getDocument().addDocumentListener(new DocumentListener() {
            @Override public void insertUpdate(DocumentEvent e) { filterList(); }
            @Override public void removeUpdate(DocumentEvent e) { filterList(); }
            @Override public void changedUpdate(DocumentEvent e) { filterList(); }
        });
    }

    private void installListRenderer() {
        list.setCellRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList<?> jList, Object value, int index,
                                                           boolean isSelected, boolean cellHasFocus) {
                JLabel label = (JLabel) super.getListCellRendererComponent(
                        jList, value, index, isSelected, cellHasFocus);
                if (value instanceof MemoItem) {
                    MemoItem item = (MemoItem) value;
                    String title = escapeHtml(item.displayTitle());
                    String date = item.displayDate();
                    String preview = item.contentPreview();
                    String tagsStr = item.tagsDisplay();

                    StringBuilder html = new StringBuilder(160);
                    html.append("<html><body style='width:180px'>");
                    if (item.pinned) {
                        html.append("<span style='color:#E67E22;font-size:11px;'>&#9733; </span>");
                    }
                    html.append("<b>").append(title).append("</b>");
                    if (!date.isEmpty()) {
                        html.append("<br><span style='font-size:10px;color:")
                                .append(isSelected ? "#inherit" : "#888888")
                                .append("'>").append(date).append("</span>");
                    }
                    if (!tagsStr.isEmpty()) {
                        html.append("<br><span style='font-size:9px;color:")
                                .append(isSelected ? "#inherit" : "#4A90D9")
                                .append("'>[").append(escapeHtml(tagsStr)).append("]</span>");
                    }
                    if (!preview.isEmpty()) {
                        html.append("<br><span style='font-size:10px;color:")
                                .append(isSelected ? "#inherit" : "#AAAAAA")
                                .append("'>").append(escapeHtml(preview)).append("</span>");
                    }
                    html.append("</body></html>");
                    label.setText(html.toString());
                }
                if (currentTheme != MemoUiTheme.IDE_DEFAULT) {
                    MemoThemeSpec spec = MemoThemeSpec.forTheme(currentTheme);
                    label.setOpaque(true);
                    if (isSelected) {
                        label.setBackground(spec.listSelectedBg);
                        label.setForeground(spec.listSelectedFg);
                    } else {
                        label.setBackground(spec.listBg);
                        label.setForeground(spec.text);
                    }
                }
                label.setBorder(JBUI.Borders.empty(5, 10, 5, 10));
                return label;
            }
        });
    }

    private static String escapeHtml(String text) {
        if (text == null) return "";
        return text.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;");
    }

    private void applyTheme(@NotNull MemoUiTheme theme) {
        currentTheme = theme;
        installListRenderer();
        list.repaint();

        if (theme == MemoUiTheme.IDE_DEFAULT) {
            applyIdeTheme();
            return;
        }

        MemoThemeSpec spec = MemoThemeSpec.forTheme(theme);

        Color panelBg = spec.panelBg;
        setBackground(panelBg);
        topStack.setOpaque(true);
        topStack.setBackground(panelBg);
        toolbar.setOpaque(true);
        toolbar.setBackground(panelBg);
        themeChooserLabel.setForeground(spec.text);
        split.setBackground(panelBg);
        leftPanel.setBackground(panelBg);
        searchPanel.setBackground(panelBg);
        searchIcon.setForeground(spec.text);
        listScroll.setBackground(panelBg);
        listScroll.getViewport().setBackground(spec.listBg);
        list.setBackground(spec.listBg);
        list.setForeground(spec.text);
        right.setBackground(panelBg);
        titleBlock.setBackground(panelBg);
        contentBlock.setBackground(panelBg);
        tagBlock.setBackground(panelBg);
        tagDisplayPanel.setBackground(panelBg);
        titleLabel.setForeground(spec.text);
        contentLabel.setForeground(spec.text);
        tagLabel.setForeground(spec.text);
        countLabel.setForeground(spec.text);
        modifiedLabel.setForeground(spec.text);
        wordCountLabel.setForeground(spec.text);
        editorStatusBar.setBackground(panelBg);

        styleTextField(titleField, spec);
        styleTextField(searchField, spec);
        styleTextField(tagField, spec);
        styleTextArea(contentArea, contentScroll, spec);

        themeCombo.setBackground(spec.editorBg);
        themeCombo.setForeground(spec.text);
        sortCombo.setBackground(spec.editorBg);
        sortCombo.setForeground(spec.text);

        revalidate();
        repaint();
    }

    private void applyIdeTheme() {
        Color panelBg = UIUtil.getPanelBackground();
        Color editorBg = UIUtil.getTextFieldBackground();
        Color fg = UIUtil.getTextFieldForeground();
        Color listBg = UIUtil.getListBackground();
        Color labelFg = UIUtil.getLabelForeground();

        setBackground(panelBg);
        topStack.setOpaque(false);
        toolbar.setOpaque(false);
        themeChooserLabel.setForeground(labelFg);
        split.setBackground(panelBg);
        leftPanel.setBackground(panelBg);
        searchPanel.setBackground(panelBg);
        searchIcon.setForeground(labelFg);
        listScroll.setBackground(panelBg);
        listScroll.getViewport().setBackground(listBg);
        list.setBackground(listBg);
        list.setForeground(UIUtil.getTreeForeground());
        right.setBackground(panelBg);
        titleBlock.setBackground(panelBg);
        contentBlock.setBackground(panelBg);
        tagBlock.setBackground(panelBg);
        tagDisplayPanel.setBackground(panelBg);
        titleLabel.setText("标题");
        contentLabel.setText("内容");
        tagLabel.setText("标签");
        titleLabel.setFont(defaultLabelFont);
        contentLabel.setFont(defaultLabelFont);
        tagLabel.setFont(defaultLabelFont);
        titleLabel.setForeground(labelFg);
        contentLabel.setForeground(labelFg);
        tagLabel.setForeground(labelFg);
        countLabel.setForeground(JBColor.GRAY);
        modifiedLabel.setForeground(JBColor.GRAY);
        wordCountLabel.setForeground(JBColor.GRAY);
        editorStatusBar.setBackground(panelBg);

        resetFieldLook(titleField, editorBg, fg);
        resetFieldLook(searchField, editorBg, fg);
        resetFieldLook(tagField, editorBg, fg);
        resetFieldLook(contentArea, editorBg, fg);
        contentScroll.getViewport().setBackground(editorBg);
        contentArea.setCaretColor(fg);
        titleField.setCaretColor(fg);
        searchField.setCaretColor(fg);
        tagField.setCaretColor(fg);

        resetCombo(themeCombo);
        resetCombo(sortCombo);

        revalidate();
        repaint();
    }

    private static void resetFieldLook(javax.swing.text.JTextComponent c, Color bg, Color fg) {
        c.setOpaque(true);
        c.setBackground(bg);
        c.setForeground(fg);
        c.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(UIUtil.getBoundsColor(), 1),
                JBUI.Borders.empty(4, 8)));
        Color selBg = UIManager.getColor("TextArea.selectionBackground");
        if (selBg == null) selBg = UIUtil.getListSelectionBackground();
        Color selFg = UIManager.getColor("TextArea.selectionForeground");
        if (selFg == null) selFg = UIUtil.getListSelectionForeground();
        c.setSelectionColor(selBg);
        c.setSelectedTextColor(selFg);
    }

    private static void resetCombo(JComboBox<?> combo) {
        combo.setOpaque(true);
        combo.setBackground(null);
        combo.setForeground(null);
    }

    private static void styleTextField(JBTextField field, MemoThemeSpec spec) {
        field.setOpaque(true);
        field.setBackground(spec.editorBg);
        field.setForeground(spec.text);
        field.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(spec.fieldBorder, 1),
                JBUI.Borders.empty(4, 8)));
        field.setCaretColor(spec.text);
        field.setSelectedTextColor(spec.listSelectedFg);
        field.setSelectionColor(spec.listSelectedBg);
    }

    private static void styleTextArea(JBTextArea area, JScrollPane scroll, MemoThemeSpec spec) {
        area.setOpaque(true);
        area.setBackground(spec.editorBg);
        area.setForeground(spec.text);
        area.setBorder(JBUI.Borders.empty());
        area.setCaretColor(spec.text);
        area.setSelectedTextColor(spec.listSelectedFg);
        area.setSelectionColor(spec.listSelectedBg);
        scroll.getViewport().setBackground(spec.editorBg);
        scroll.setBorder(JBUI.Borders.compound(
                JBUI.Borders.customLine(spec.fieldBorder, 1),
                JBUI.Borders.empty(0)));
    }

    private void markModified() {
        if (currentEditingItem != null) {
            modifiedLabel.setText("已修改");
        }
    }

    private void updateWordCount() {
        String text = contentArea.getText();
        int charCount = text.length();
        int wordCount = text.isEmpty() ? 0 : text.split("[\\s,;.?!\\u3002\\uff1b\\uff0c\\uff01\\uff1f]+").length;
        int lineCount = text.isEmpty() ? 0 : text.split("\n", -1).length;
        wordCountLabel.setText(String.format("\uD83D\uDCC4 %d 字 | %d 词 | %d 行", charCount, wordCount, lineCount));
    }

    private void reloadListModel() {
        MemoProjectService svc = MemoProjectService.getInstance(project);
        allItems = new ArrayList<>(svc.getItems());
        applySortAndFilter();
    }

    private void applySortAndFilter() {
        String query = searchField.getText().toLowerCase().trim();
        int sortMode = sortCombo.getSelectedIndex();

        List<MemoItem> filtered = allItems.stream()
                .filter(item -> query.isEmpty()
                        || item.title.toLowerCase().contains(query)
                        || item.content.toLowerCase().contains(query)
                        || item.tagsDisplay().toLowerCase().contains(query))
                .sorted(getComparator(sortMode))
                .collect(Collectors.toList());

        listModel.clear();
        for (MemoItem item : filtered) {
            listModel.addElement(item);
        }
        countLabel.setText("\uD83D\uDCCB 共 " + filtered.size() + " 条");
    }

    private Comparator<MemoItem> getComparator(int sortMode) {
        switch (sortMode) {
            case 0:
                return (a, b) -> {
                    if (a.pinned != b.pinned) return a.pinned ? -1 : 1;
                    return Long.compare(b.updated, a.updated);
                };
            case 1:
                return (a, b) -> {
                    if (a.pinned != b.pinned) return a.pinned ? -1 : 1;
                    return a.displayTitle().compareToIgnoreCase(b.displayTitle());
                };
            case 2:
                return (a, b) -> {
                    if (a.pinned != b.pinned) return a.pinned ? -1 : 1;
                    return Long.compare(b.updated, a.updated);
                };
            default:
                return (a, b) -> Long.compare(b.updated, a.updated);
        }
    }

    private void filterList() {
        applySortAndFilter();
    }

    private void loadSelectedIntoEditor() {
        if (loadingSelection) return;

        MemoItem sel = list.getSelectedValue();
        if (sel == null) {
            saveCurrentEditingItem();
            titleField.setText("");
            contentArea.setText("");
            tagField.setText("");
            tagDisplayPanel.removeAll();
            tagDisplayPanel.revalidate();
            tagDisplayPanel.repaint();
            modifiedLabel.setText("");
            wordCountLabel.setText(" ");
            currentEditingItem = null;
            return;
        }

        saveCurrentEditingItem();

        loadingSelection = true;
        currentEditingItem = sel;
        titleField.setText(sel.title);
        contentArea.setText(sel.content);
        modifiedLabel.setText(sel.updated > 0 ? "\uD83D\uDCC5 " + sel.displayDate() : "");
        updateWordCount();
        refreshTagDisplay(sel);
        pinBtn.setText(sel.pinned ? "\u2B50 已置顶" : "\uD83D\uDCCC 置顶");
        loadingSelection = false;
    }

    private void saveCurrentEditingItem() {
        if (currentEditingItem == null) return;
        currentEditingItem.title = titleField.getText();
        currentEditingItem.content = contentArea.getText();
        currentEditingItem.updated = System.currentTimeMillis();
        list.repaint();
    }

    private void refreshTagDisplay(MemoItem item) {
        tagDisplayPanel.removeAll();
        if (item.tags != null) {
            for (String tag : item.tags) {
                JLabel tagLabel = new JLabel(tag);
                tagLabel.setOpaque(true);
                tagLabel.setBackground(new JBColor(new Color(0xE3F2FD), new Color(0x1A3A5C)));
                tagLabel.setForeground(new JBColor(new Color(0x1565C0), new Color(0x90CAF9)));
                tagLabel.setBorder(JBUI.Borders.compound(
                        JBUI.Borders.customLine(new JBColor(new Color(0xBBDEFB), new Color(0x2A4A6C)), 1),
                        JBUI.Borders.empty(2, 8)));
                tagLabel.setFont(tagLabel.getFont().deriveFont(Font.PLAIN, 11f));
                tagDisplayPanel.add(tagLabel);
            }
        }
        tagDisplayPanel.revalidate();
        tagDisplayPanel.repaint();
    }

    private void addTagFromField() {
        String tag = tagField.getText().trim();
        if (tag.isEmpty()) return;

        if (currentEditingItem == null) {
            JOptionPane.showMessageDialog(this, "请先选择一条备忘录");
            return;
        }

        if (currentEditingItem.tags == null) {
            currentEditingItem.tags = new ArrayList<>();
        }
        if (!currentEditingItem.tags.contains(tag)) {
            currentEditingItem.tags.add(tag);
            tagField.setText("");
            refreshTagDisplay(currentEditingItem);
            list.repaint();
        }
    }

    private void togglePin() {
        MemoItem sel = list.getSelectedValue();
        if (sel == null) return;

        saveCurrentEditingItem();

        sel.pinned = !sel.pinned;
        pinBtn.setText(sel.pinned ? "\u2B50 已置顶" : "\uD83D\uDCCC 置顶");

        loadingSelection = true;
        applySortAndFilter();

        for (int i = 0; i < listModel.size(); i++) {
            if (listModel.get(i).id.equals(sel.id)) {
                list.setSelectedIndex(i);
                break;
            }
        }
        loadingSelection = false;
    }

    private void createMemo() {
        saveCurrentEditingItem();

        MemoProjectService svc = MemoProjectService.getInstance(project);
        MemoItem m = svc.createMemo();
        m.title = "新备忘录";
        m.content = "";
        m.updated = System.currentTimeMillis();
        allItems.add(m);

        loadingSelection = true;
        applySortAndFilter();

        for (int i = 0; i < listModel.size(); i++) {
            if (listModel.get(i).id.equals(m.id)) {
                list.setSelectedIndex(i);
                list.ensureIndexIsVisible(i);
                break;
            }
        }
        loadingSelection = false;

        currentEditingItem = m;
        titleField.setText(m.title);
        contentArea.setText(m.content);
        modifiedLabel.setText("");
        updateWordCount();
        refreshTagDisplay(m);
        pinBtn.setText(m.pinned ? "\u2B50 已置顶" : "\uD83D\uDCCC 置顶");
        titleField.requestFocus();
        titleField.selectAll();
    }

    private void saveCurrent() {
        if (currentEditingItem == null) return;

        currentEditingItem.title = titleField.getText();
        currentEditingItem.content = contentArea.getText();
        currentEditingItem.updated = System.currentTimeMillis();
        modifiedLabel.setText("\u2705 已保存 " + currentEditingItem.displayDate());
        list.repaint();
    }

    private void saveCurrentSilent() {
        if (currentEditingItem == null) return;

        currentEditingItem.title = titleField.getText();
        currentEditingItem.content = contentArea.getText();
        currentEditingItem.updated = System.currentTimeMillis();
        list.repaint();
    }

    private void deleteCurrent() {
        MemoItem sel = list.getSelectedValue();
        if (sel == null) return;

        int confirm = JOptionPane.showConfirmDialog(this,
                "确定要删除\u300C" + sel.displayTitle() + "\u300D吗？\n此操作不可撤销。",
                "删除确认", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
        if (confirm != JOptionPane.YES_OPTION) return;

        saveCurrentEditingItem();

        if (currentEditingItem != null && currentEditingItem.id.equals(sel.id)) {
            currentEditingItem = null;
        }

        MemoProjectService svc = MemoProjectService.getInstance(project);
        svc.removeById(sel.id);
        allItems.remove(sel);

        loadingSelection = true;
        applySortAndFilter();

        if (listModel.size() > 0) {
            list.setSelectedIndex(0);
            loadingSelection = false;
            MemoItem first = list.getSelectedValue();
            if (first != null) {
                currentEditingItem = first;
                titleField.setText(first.title);
                contentArea.setText(first.content);
                modifiedLabel.setText(first.updated > 0 ? "\uD83D\uDCC5 " + first.displayDate() : "");
                updateWordCount();
                refreshTagDisplay(first);
                pinBtn.setText(first.pinned ? "\u2B50 已置顶" : "\uD83D\uDCCC 置顶");
            }
        } else {
            loadingSelection = false;
            currentEditingItem = null;
            titleField.setText("");
            contentArea.setText("");
            tagField.setText("");
            tagDisplayPanel.removeAll();
            tagDisplayPanel.revalidate();
            tagDisplayPanel.repaint();
            modifiedLabel.setText("");
            wordCountLabel.setText(" ");
        }
    }

    private void refreshSelectedTitleInList() {
        if (currentEditingItem == null || loadingSelection) return;
        currentEditingItem.title = titleField.getText();
        list.repaint();
    }

    private void exportMemos() {
        saveCurrentEditingItem();

        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("导出备忘录");
        chooser.setSelectedFile(new java.io.File("memos_export.json"));
        chooser.setFileFilter(new FileNameExtensionFilter("JSON 文件 (*.json)", "json"));

        if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) return;

        java.io.File file = chooser.getSelectedFile();
        if (!file.getName().endsWith(".json")) {
            file = new java.io.File(file.getAbsolutePath() + ".json");
        }

        try {
            StringBuilder sb = new StringBuilder();
            sb.append("[\n");
            for (int i = 0; i < allItems.size(); i++) {
                MemoItem item = allItems.get(i);
                sb.append("  {\n");
                sb.append("    \"id\": \"").append(escapeJson(item.id)).append("\",\n");
                sb.append("    \"title\": \"").append(escapeJson(item.title)).append("\",\n");
                sb.append("    \"content\": \"").append(escapeJson(item.content)).append("\",\n");
                sb.append("    \"updated\": ").append(item.updated).append(",\n");
                sb.append("    \"pinned\": ").append(item.pinned).append(",\n");
                sb.append("    \"tags\": [");
                if (item.tags != null) {
                    for (int j = 0; j < item.tags.size(); j++) {
                        if (j > 0) sb.append(", ");
                        sb.append("\"").append(escapeJson(item.tags.get(j))).append("\"");
                    }
                }
                sb.append("]\n");
                sb.append("  }");
                if (i < allItems.size() - 1) sb.append(",");
                sb.append("\n");
            }
            sb.append("]\n");
            java.nio.file.Files.writeString(file.toPath(), sb.toString(), StandardCharsets.UTF_8);
            JOptionPane.showMessageDialog(this, "已导出 " + allItems.size() + " 条备忘录到:\n" + file);
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "导出失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void importMemos() {
        saveCurrentEditingItem();

        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("导入备忘录");
        chooser.setFileFilter(new FileNameExtensionFilter("JSON 文件 (*.json)", "json"));

        if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;

        try {
            String json = java.nio.file.Files.readString(chooser.getSelectedFile().toPath(), StandardCharsets.UTF_8);
            List<MemoItem> imported = parseMemoJson(json);
            if (imported.isEmpty()) {
                JOptionPane.showMessageDialog(this, "未找到有效的备忘录数据");
                return;
            }

            MemoProjectService svc = MemoProjectService.getInstance(project);
            int added = 0;
            for (MemoItem item : imported) {
                if (svc.findById(item.id) == null) {
                    svc.getItems().add(item);
                    allItems.add(item);
                    added++;
                }
            }
            if (added > 0) {
                applySortAndFilter();
                JOptionPane.showMessageDialog(this, "成功导入 " + added + " 条备忘录");
            } else {
                JOptionPane.showMessageDialog(this, "所有备忘录已存在，未导入新数据");
            }
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this, "导入失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
    }

    private static String escapeJson(String s) {
        if (s == null) return "";
        return s.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }

    private static List<MemoItem> parseMemoJson(String json) {
        List<MemoItem> result = new ArrayList<>();
        try {
            com.google.gson.Gson gson = new com.google.gson.Gson();
            MemoItem[] items = gson.fromJson(json, MemoItem[].class);
            if (items != null) {
                for (MemoItem item : items) {
                    if (item.id != null && !item.id.isEmpty()) {
                        if (item.tags == null) item.tags = new ArrayList<>();
                        result.add(item);
                    }
                }
            }
        } catch (Exception ignored) {
            try {
                json = json.trim();
                if (json.startsWith("[")) {
                    int braceCount = 0;
                    int start = -1;
                    for (int i = 0; i < json.length(); i++) {
                        char c = json.charAt(i);
                        if (c == '{') {
                            if (braceCount == 0) start = i;
                            braceCount++;
                        } else if (c == '}') {
                            braceCount--;
                            if (braceCount == 0 && start >= 0) {
                                String itemJson = json.substring(start, i + 1);
                                MemoItem item = parseSingleMemo(itemJson);
                                if (item != null) result.add(item);
                                start = -1;
                            }
                        }
                    }
                }
            } catch (Exception ignored2) {}
        }
        return result;
    }

    private static MemoItem parseSingleMemo(String json) {
        try {
            MemoItem item = new MemoItem();
            String idField = extractJsonField(json, "id");
            if (idField != null) item.id = idField;
            item.title = extractJsonField(json, "title");
            item.content = extractJsonField(json, "content");
            String updatedStr = extractJsonField(json, "updated");
            if (updatedStr != null) {
                try { item.updated = Long.parseLong(updatedStr); } catch (NumberFormatException ignored) {}
            }
            String pinnedStr = extractJsonField(json, "pinned");
            item.pinned = "true".equals(pinnedStr);
            String tagsStr = extractJsonArray(json, "tags");
            if (tagsStr != null && !tagsStr.isEmpty()) {
                item.tags = new ArrayList<>();
                for (String t : tagsStr.split(",")) {
                    String clean = t.replace("\"", "").trim();
                    if (!clean.isEmpty()) item.tags.add(clean);
                }
            }
            if (item.tags == null) item.tags = new ArrayList<>();
            return item.id.isEmpty() ? null : item;
        } catch (Exception e) {
            return null;
        }
    }

    private static String extractJsonField(String json, String field) {
        String key = "\"" + field + "\"";
        int idx = json.indexOf(key);
        if (idx < 0) return null;
        idx = json.indexOf(':', idx + key.length());
        if (idx < 0) return null;
        idx++;
        while (idx < json.length() && json.charAt(idx) == ' ') idx++;
        if (idx >= json.length()) return null;
        if (json.charAt(idx) == '"') {
            StringBuilder sb = new StringBuilder();
            idx++;
            while (idx < json.length()) {
                char c = json.charAt(idx);
                if (c == '\\') {
                    idx++;
                    if (idx < json.length()) sb.append(json.charAt(idx));
                } else if (c == '"') {
                    break;
                } else {
                    sb.append(c);
                }
                idx++;
            }
            return sb.toString();
        } else {
            StringBuilder sb = new StringBuilder();
            while (idx < json.length() && (Character.isDigit(json.charAt(idx)) || json.charAt(idx) == '-'
                    || json.charAt(idx) == '.' || json.charAt(idx) == 'e' || json.charAt(idx) == 'E'
                    || json.charAt(idx) == '+' || json.charAt(idx) == 't' || json.charAt(idx) == 'f')) {
                sb.append(json.charAt(idx));
                idx++;
            }
            return sb.toString().trim();
        }
    }

    private static String extractJsonArray(String json, String field) {
        String key = "\"" + field + "\"";
        int idx = json.indexOf(key);
        if (idx < 0) return null;
        idx = json.indexOf('[', idx + key.length());
        if (idx < 0) return null;
        idx++;
        int end = json.indexOf(']', idx);
        if (end < 0) return null;
        return json.substring(idx, end).trim();
    }
}