package cz.kamma.kfmanager.ui;

import cz.kamma.kfmanager.config.AppConfig;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

/**
 * Settings dialog with categories on the left and parameters on the right.
 */
public class SettingsDialog extends JDialog {
    private final AppConfig config;
    private final Runnable onChange;
    private final JList<String> categoryList;
    private final JPanel cards;
    private final CardLayout cardLayout;

    // Original values for cancellation
    private final Color originalBg;
    private final Color originalSel;
    private final Color originalMark;
    private final Color originalFolder;
    private final int originalBriefMouseWheelSteps;
    private final Font originalGlobalFont;
    private final Font originalEditorFont;
    private final String originalExternalEditorPath;
    private final int originalToolbarButtonSize;
    private final int originalToolbarIconSize;
    private final java.util.Map<String, String> originalFileAssociations;

    // Appearance controls
    private JButton appearanceFontBtn;
    private JButton appearanceBgBtn;
    private JButton appearanceSelBtn;
    private JButton appearanceMarkBtn;
    private JButton appearanceFolderBtn;

    // Editor controls
    private JButton editorFontBtn;
    private JTextField externalEditorField;

    private final Map<String, JPanel> panels = new HashMap<>();

    public SettingsDialog(Window parent, AppConfig config, Runnable onChange) {
        super(parent, "Settings", ModalityType.APPLICATION_MODAL);
        this.config = config;
        this.onChange = onChange;

        // Store original values
        this.originalBg = config.getBackgroundColor();
        this.originalSel = config.getSelectionColor();
        this.originalMark = config.getMarkedColor();
        this.originalFolder = config.getFolderColor();
        this.originalBriefMouseWheelSteps = config.getBriefMouseWheelSteps();
        this.originalGlobalFont = config.getGlobalFont();
        this.originalEditorFont = config.getEditorFont();
        this.originalExternalEditorPath = config.getExternalEditorPath();
        this.originalToolbarButtonSize = config.getToolbarButtonSize();
        this.originalToolbarIconSize = config.getToolbarIconSize();
        this.originalFileAssociations = new java.util.HashMap<>(config.getFileAssociations());

        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(700, 420);
        setLocationRelativeTo(parent);

        // Left: categories
    DefaultListModel<String> model = new DefaultListModel<>();
    model.addElement("Appearance");
    model.addElement("Editor");
    model.addElement("Behavior");
    model.addElement("Sorting");
    model.addElement("Toolbar");
    model.addElement("Associations");
        categoryList = new JList<>(model);
        categoryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        categoryList.setSelectedIndex(0);

        cardLayout = new CardLayout();
        cards = new JPanel(cardLayout);

        // Build category panels
    cards.add(buildAppearancePanel(), "Appearance");
    cards.add(buildEditorPanel(), "Editor");
    cards.add(buildBehaviorPanel(), "Behavior");
    cards.add(buildSortingPanel(), "Sorting");
    cards.add(buildToolbarPanel(), "Toolbar");
    cards.add(buildAssociationsPanel(), "Associations");

        categoryList.addListSelectionListener(e -> {
            if (!e.getValueIsAdjusting()) {
                String sel = categoryList.getSelectedValue();
                if (sel != null) cardLayout.show(cards, sel);
            }
        });

        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                new JScrollPane(categoryList), cards);
        split.setResizeWeight(0);
        split.setDividerLocation(160);

        add(split, BorderLayout.CENTER);

        JPanel btns = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        JButton ok = new JButton("OK");
        ok.addActionListener(e -> {
            // Collect sorting settings from the Sorting panel (if present)
            JPanel sortingHolder = (JPanel) panels.get("Sorting");
            if (sortingHolder != null) {
                try {
                    JComboBox<?> pf = (JComboBox<?>) sortingHolder.getClientProperty("primaryField");
                    JComboBox<?> po = (JComboBox<?>) sortingHolder.getClientProperty("primaryOrder");
                    JComboBox<?> sf = (JComboBox<?>) sortingHolder.getClientProperty("secondaryField");
                    JComboBox<?> so = (JComboBox<?>) sortingHolder.getClientProperty("secondaryOrder");
                    JComboBox<?> tf = (JComboBox<?>) sortingHolder.getClientProperty("tertiaryField");
                    JComboBox<?> to = (JComboBox<?>) sortingHolder.getClientProperty("tertiaryOrder");

                    java.util.List<String> criteria = new java.util.ArrayList<>();

                    if (pf != null && po != null) {
                        String f = pf.getSelectedItem() != null ? pf.getSelectedItem().toString().toLowerCase() : "";
                        String ord = po.getSelectedItem() != null && po.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
                        if (!f.isEmpty()) criteria.add(f + ":" + ord);
                    }

                    if (sf != null && so != null) {
                        String s = sf.getSelectedItem() != null ? sf.getSelectedItem().toString() : "(none)";
                        if (!"(none)".equals(s)) {
                            String field = s.toLowerCase();
                            String ord = so.getSelectedItem() != null && so.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
                            criteria.add(field + ":" + ord);
                        }
                    }

                    if (tf != null && to != null) {
                        String t = tf.getSelectedItem() != null ? tf.getSelectedItem().toString() : "(none)";
                        if (!"(none)".equals(t)) {
                            String field = t.toLowerCase();
                            String ord = to.getSelectedItem() != null && to.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
                            criteria.add(field + ":" + ord);
                        }
                    }

                    config.setMultipleSortCriteria(criteria);

                    // save extra sorting options
                    JComboBox<?> hiddenOrder = (JComboBox<?>) sortingHolder.getClientProperty("hiddenOrder");
                    JCheckBox uppercasePriority = (JCheckBox) sortingHolder.getClientProperty("uppercasePriority");
                    JCheckBox numericAware = (JCheckBox) sortingHolder.getClientProperty("numericAware");
                    if (hiddenOrder != null) {
                        boolean hiddenLast = "Hidden last".equals(hiddenOrder.getSelectedItem());
                        config.setHiddenFilesLast(hiddenLast);
                    }
                    if (uppercasePriority != null) {
                        config.setUppercasePriority(uppercasePriority.isSelected());
                    }
                    if (numericAware != null) {
                        config.setNumericSortEnabled(numericAware.isSelected());
                    }
                    JCheckBox ignoreLeadingDot = (JCheckBox) sortingHolder.getClientProperty("ignoreLeadingDot");
                    if (ignoreLeadingDot != null) {
                        config.setIgnoreLeadingDot(ignoreLeadingDot.isSelected());
                    }
                } catch (Exception ignore) {}
            }

            // Collect Toolbar settings
            JPanel toolbarHolder = (JPanel) panels.get("Toolbar");
            if (toolbarHolder != null) {
                try {
                    JSpinner bs = (JSpinner) toolbarHolder.getClientProperty("buttonSize");
                    JSpinner is = (JSpinner) toolbarHolder.getClientProperty("iconSize");
                    if (bs != null) config.setToolbarButtonSize((Integer) bs.getValue());
                    if (is != null) config.setToolbarIconSize((Integer) is.getValue());
                } catch (Exception ignore) {}
            }

            // Collect Associations settings
            JPanel assocHolder = (JPanel) panels.get("Associations");
            if (assocHolder != null) {
                try {
                    DefaultTableModel assocModel = (DefaultTableModel) assocHolder.getClientProperty("tableModel");
                    if (assocModel != null) {
                        java.util.Map<String, String> map = new java.util.HashMap<>();
                        for (int i = 0; i < assocModel.getRowCount(); i++) {
                            String ext = (String) assocModel.getValueAt(i, 0);
                            String cmd = (String) assocModel.getValueAt(i, 1);
                            if (ext != null && !ext.trim().isEmpty() && cmd != null && !cmd.trim().isEmpty()) {
                                map.put(ext.trim(), cmd.trim());
                            }
                        }
                        config.setFileAssociations(map);
                    }
                } catch (Exception ignore) {}
            }
            
            // Save external editor path
            if (externalEditorField != null) {
                config.setExternalEditorPath(externalEditorField.getText());
            }

            // Collect Behavior settings
            JPanel behaviorHolder = (JPanel) panels.get("Behavior");
            if (behaviorHolder != null) {
                try {
                    JSpinner mws = (JSpinner) behaviorHolder.getClientProperty("mouseWheelSteps");
                    if (mws != null) config.setBriefMouseWheelSteps((Integer) mws.getValue());
                } catch (Exception ignore) {}
            }

            // Persist config and notify caller
            config.saveConfig();
            if (onChange != null) onChange.run();
            dispose();
        });
        JButton cancel = new JButton("Cancel");
        cancel.addActionListener(e -> {
            // Restore original values
            config.setBackgroundColor(originalBg);
            config.setSelectionColor(originalSel);
            config.setMarkedColor(originalMark);
            config.setFolderColor(originalFolder);
            config.setBriefMouseWheelSteps(originalBriefMouseWheelSteps);
            config.setGlobalFont(originalGlobalFont);
            config.setEditorFont(originalEditorFont);
            config.setExternalEditorPath(originalExternalEditorPath);
            config.setToolbarButtonSize(originalToolbarButtonSize);
            config.setToolbarIconSize(originalToolbarIconSize);
            config.setFileAssociations(originalFileAssociations);
            
            // Notify UI to revert changes
            if (onChange != null) onChange.run();
            
            dispose();
        });
        btns.add(ok);
        btns.add(cancel);
        add(btns, BorderLayout.SOUTH);
    }

    private JPanel buildAppearancePanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        JPanel grid = new JPanel(new GridBagLayout());
        grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
        
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.weightx = 1.0;
        
        int row = 0;

        // Application font
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Application font:"), gbc);
        
        appearanceFontBtn = new JButton(getFontDescription(config.getGlobalFont()));
        appearanceFontBtn.addActionListener(e -> {
            Font nf = FontChooserDialog.showDialog(this, config.getGlobalFont());
            if (nf != null) {
                config.setGlobalFont(nf);
                appearanceFontBtn.setText(getFontDescription(nf));
                if (onChange != null) onChange.run();
            }
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(appearanceFontBtn, gbc);

        // Background color
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Background color:"), gbc);
        
        appearanceBgBtn = createColorButton(config.getBackgroundColor(), "Choose background color", c -> {
            config.setBackgroundColor(c);
            if (onChange != null) onChange.run();
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(appearanceBgBtn, gbc);

        // Selection color
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Selection color:"), gbc);
        
        appearanceSelBtn = createColorButton(config.getSelectionColor() != null ? config.getSelectionColor() : new Color(184, 207, 229), 
                "Choose selection color", c -> {
            config.setSelectionColor(c);
            if (onChange != null) onChange.run();
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(appearanceSelBtn, gbc);

        // Marked item color
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Marked item color:"), gbc);
        
        appearanceMarkBtn = createColorButton(config.getMarkedColor() != null ? config.getMarkedColor() : new Color(204, 153, 0), 
                "Choose marked item color", c -> {
            config.setMarkedColor(c);
            if (onChange != null) onChange.run();
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(appearanceMarkBtn, gbc);

        // Folder icon color
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Folder icon color:"), gbc);
        
        appearanceFolderBtn = createColorButton(config.getFolderColor(), "Choose folder icon color", c -> {
            config.setFolderColor(c);
            if (onChange != null) onChange.run();
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(appearanceFolderBtn, gbc);

        p.add(grid, BorderLayout.NORTH);
        panels.put("Appearance", p);
        return p;
    }

    private JButton createColorButton(Color initialColor, String title, Consumer<Color> onColorChosen) {
        JButton btn = new JButton(" ") {
            @Override
            public void paintComponent(Graphics g) {
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth(), getHeight());
                if (getModel().isRollover()) {
                    g.setColor(new Color(255, 255, 255, 60));
                    g.fillRect(0, 0, getWidth(), getHeight());
                }
                if (getModel().isPressed()) {
                    g.setColor(new Color(0, 0, 0, 60));
                    g.fillRect(0, 0, getWidth(), getHeight());
                }
            }
        };
        btn.setOpaque(true);
        btn.setContentAreaFilled(false);
        btn.setFocusPainted(false);
        btn.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        btn.setPreferredSize(new Dimension(80, 25));
        btn.setBackground(initialColor != null ? initialColor : UIManager.getColor("Panel.background"));
        btn.addActionListener(e -> {
            Color chosen = JColorChooser.showDialog(this, title, btn.getBackground());
            if (chosen != null) {
                btn.setBackground(chosen);
                onColorChosen.accept(chosen);
            }
        });
        return btn;
    }

    private JPanel buildEditorPanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        JPanel grid = new JPanel(new GridBagLayout());
        grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.weightx = 1.0;
        
        int row = 0;

        // Editor font
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Editor font:"), gbc);
        
        editorFontBtn = new JButton(getFontDescription(config.getEditorFont()));
        editorFontBtn.addActionListener(e -> {
            Font nf = FontChooserDialog.showDialog(this, config.getEditorFont());
            if (nf != null) {
                config.setEditorFont(nf);
                editorFontBtn.setText(getFontDescription(nf));
                if (onChange != null) onChange.run();
            }
        });
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(editorFontBtn, gbc);

        // External editor path
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("External editor:"), gbc);
        
        JPanel extPanel = new JPanel(new BorderLayout(6, 0));
        externalEditorField = new JTextField(config.getExternalEditorPath());
        JButton browse = new JButton("Browse...");
        browse.addActionListener(e -> {
            JFileChooser fc = new JFileChooser();
            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
            fc.setDialogTitle("Select external editor executable");
            if (!externalEditorField.getText().isEmpty()) {
                File f = new File(externalEditorField.getText());
                if (f.exists()) fc.setSelectedFile(f);
            }
            int r = fc.showOpenDialog(this);
            if (r == JFileChooser.APPROVE_OPTION) {
                File sel = fc.getSelectedFile();
                externalEditorField.setText(sel.getAbsolutePath());
                config.setExternalEditorPath(sel.getAbsolutePath());
                // config.saveConfig() will be called when OK is pressed
                if (onChange != null) onChange.run();
            }
        });
        extPanel.add(externalEditorField, BorderLayout.CENTER);
        extPanel.add(browse, BorderLayout.EAST);
        
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(extPanel, gbc);

        p.add(grid, BorderLayout.NORTH);
        panels.put("Editor", p);
        return p;
    }

    private JPanel buildBehaviorPanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        JPanel grid = new JPanel(new GridBagLayout());
        grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.weightx = 1.0;

        int row = 0;

        // Mouse wheel steps in brief mode
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Mouse wheel steps (Brief mode):"), gbc);

        JSpinner mwSteps = new JSpinner(new SpinnerNumberModel(config.getBriefMouseWheelSteps(), 1, 100, 1));
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(mwSteps, gbc);

        p.add(grid, BorderLayout.NORTH);
        p.putClientProperty("mouseWheelSteps", mwSteps);
        panels.put("Behavior", p);
        return p;
    }

    private JPanel buildSortingPanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        JPanel grid = new JPanel();
        grid.setLayout(new BoxLayout(grid, BoxLayout.Y_AXIS));
        grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));

        // Helper to add a label above a control for single-column layout
        java.util.function.BiConsumer<String, JComponent> addLabeled = (labelText, comp) -> {
            JPanel row = new JPanel();
            row.setLayout(new BoxLayout(row, BoxLayout.Y_AXIS));
            JLabel lbl = new JLabel(labelText);
            lbl.setAlignmentX(Component.LEFT_ALIGNMENT);
            comp.setAlignmentX(Component.LEFT_ALIGNMENT);
            row.add(lbl);
            row.add(Box.createVerticalStrut(4));
            row.add(comp);
            row.setAlignmentX(Component.LEFT_ALIGNMENT);
            grid.add(row);
            grid.add(Box.createVerticalStrut(8));
        };

        JComboBox<String> primaryField = new JComboBox<>(new String[]{"Name", "Size", "Date"});
        JComboBox<String> primaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
        JPanel primaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
        primaryPanel.add(primaryField);
        primaryPanel.add(primaryOrder);
        addLabeled.accept("Primary sort:", primaryPanel);

        JComboBox<String> secondaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"});
        JComboBox<String> secondaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
        JPanel secondaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
        secondaryPanel.add(secondaryField);
        secondaryPanel.add(secondaryOrder);
        addLabeled.accept("Secondary sort:", secondaryPanel);

        JComboBox<String> tertiaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"});
        JComboBox<String> tertiaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
        JPanel tertiaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
        tertiaryPanel.add(tertiaryField);
        tertiaryPanel.add(tertiaryOrder);
        addLabeled.accept("Tertiary sort:", tertiaryPanel);

        // Additional sorting options (each as own labeled row)
        JComboBox<String> hiddenOrder = new JComboBox<>(new String[]{"Hidden last", "Hidden first"});
        hiddenOrder.setSelectedIndex(config.getHiddenFilesLast() ? 0 : 1);
        addLabeled.accept("Hidden files:", hiddenOrder);

        JCheckBox uppercasePriority = new JCheckBox("Prefer uppercase first");
        uppercasePriority.setSelected(config.getUppercasePriority());
        addLabeled.accept("Uppercase priority:", uppercasePriority);

        JCheckBox numericAware = new JCheckBox("Enable natural numeric sorting");
        numericAware.setSelected(config.getNumericSortEnabled());
        addLabeled.accept("Numeric-aware names:", numericAware);

    JCheckBox ignoreLeadingDot = new JCheckBox("Ignore leading dot in names (treat '.name' as 'name')");
    ignoreLeadingDot.setSelected(config.getIgnoreLeadingDot());
    addLabeled.accept("Ignore leading dot:", ignoreLeadingDot);

        // Load existing criteria from config
        java.util.List<String> crit = config.getMultipleSortCriteria();
        if (crit != null && !crit.isEmpty()) {
            try {
                // parse strings like "name:asc"
                if (crit.size() > 0) {
                    String[] parts = crit.get(0).split(":");
                    if (parts.length >= 1) primaryField.setSelectedItem(capitalize(parts[0]));
                    if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) primaryOrder.setSelectedItem("Descending");
                }
                if (crit.size() > 1) {
                    String[] parts = crit.get(1).split(":");
                    if (parts.length >= 1) secondaryField.setSelectedItem("(" + parts[0] + ")");
                    if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) secondaryOrder.setSelectedItem("Descending");
                }
                if (crit.size() > 2) {
                    String[] parts = crit.get(2).split(":");
                    if (parts.length >= 1) tertiaryField.setSelectedItem("(" + parts[0] + ")");
                    if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) tertiaryOrder.setSelectedItem("Descending");
                }
            } catch (Exception ignore) {}
        }

        p.add(grid, BorderLayout.NORTH);

        // Save action will be done on OK; store controls in panels map so OK handler can read them
    JPanel holder = new JPanel();
        holder.putClientProperty("primaryField", primaryField);
        holder.putClientProperty("primaryOrder", primaryOrder);
        holder.putClientProperty("secondaryField", secondaryField);
        holder.putClientProperty("secondaryOrder", secondaryOrder);
        holder.putClientProperty("tertiaryField", tertiaryField);
        holder.putClientProperty("tertiaryOrder", tertiaryOrder);
    holder.putClientProperty("hiddenOrder", hiddenOrder);
    holder.putClientProperty("uppercasePriority", uppercasePriority);
    holder.putClientProperty("numericAware", numericAware);
    holder.putClientProperty("ignoreLeadingDot", ignoreLeadingDot);
        panels.put("Sorting", holder);

        return p;
    }

    private JPanel buildToolbarPanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        JPanel grid = new JPanel(new GridBagLayout());
        grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.weightx = 1.0;
        
        int row = 0;

        // Button size
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Button size (px):"), gbc);
        
        JSpinner buttonSizeSpinner = new JSpinner(new SpinnerNumberModel(config.getToolbarButtonSize(), 16, 128, 1));
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(buttonSizeSpinner, gbc);

        // Icon size
        gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
        grid.add(new JLabel("Icon size (px):"), gbc);
        
        JSpinner iconSizeSpinner = new JSpinner(new SpinnerNumberModel(config.getToolbarIconSize(), 16, 128, 1));
        gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
        grid.add(iconSizeSpinner, gbc);

        p.add(grid, BorderLayout.NORTH);
        
        // Save action will be done on OK; store controls in a holder
        JPanel holder = new JPanel();
        holder.putClientProperty("buttonSize", buttonSizeSpinner);
        holder.putClientProperty("iconSize", iconSizeSpinner);
        panels.put("Toolbar", holder);
        
        return p;
    }

    private JPanel buildAssociationsPanel() {
        JPanel p = new JPanel(new BorderLayout(8, 8));
        p.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));

        JLabel hint = new JLabel("<html>Use <b>wildcards</b> (e.g. *.tar.gz) or plain extension (e.g. txt).<br>" +
                                "Use <b>%f</b> for full path, <b>%n</b> for filename. " +
                                "If no placeholder is used, path is appended to the end.</html>");
        p.add(hint, BorderLayout.NORTH);

        DefaultTableModel model = new DefaultTableModel(new String[]{"Pattern", "Command"}, 0);
        java.util.Map<String, String> current = config.getFileAssociations();
        for (java.util.Map.Entry<String, String> entry : current.entrySet()) {
            model.addRow(new Object[]{entry.getKey(), entry.getValue()});
        }

        JTable table = new JTable(model);
        p.add(new JScrollPane(table), BorderLayout.CENTER);

        JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        JButton addBtn = new JButton("Add");
        addBtn.addActionListener(e -> model.addRow(new Object[]{"", ""}));
        JButton removeBtn = new JButton("Remove");
        removeBtn.addActionListener(e -> {
            int row = table.getSelectedRow();
            if (row >= 0) model.removeRow(row);
        });
        btnPanel.add(addBtn);
        btnPanel.add(removeBtn);
        p.add(btnPanel, BorderLayout.SOUTH);

        // Save reference for OK action
        JPanel holder = new JPanel();
        holder.putClientProperty("tableModel", model);
        panels.put("Associations", holder);

        return p;
    }

    private static String capitalize(String s) {
        if (s == null || s.isEmpty()) return s;
        return s.substring(0,1).toUpperCase() + s.substring(1).toLowerCase();
    }

    private String getFontDescription(Font f) {
        if (f == null) return "(default)";
        return String.format("%s %dpt", f.getName(), f.getSize());
    }
}
