package cz.kamma.llamarunner;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Image;
import javax.imageio.ImageIO;
import java.awt.datatransfer.StringSelection;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * Main Swing application class for managing llama-server.
 */
public class Main extends JFrame {

    private final AppConfig appConfig;
    private final ConfigLocation configLocation;
    private final ProfileManager profileManager;
    private final CommandBuilder commandBuilder;
    private boolean isProfileLoading = false;
    private java.awt.event.ActionListener profileChangeListener;

    private JTextField hostField;
    private JTextField portField;
    private JTextField parallelField;
    private JTextField batchSizeField;
    private JTextField threadsField;
    private JCheckBox flashAttnCheckBox;
    private JCheckBox kvUnifiedCheckBox;
    private JCheckBox fitCheckBox;
    private JCheckBox reasoningCheckBox;
    private JComboBox<String> cacheTypeKComboBox;
    private JComboBox<String> cacheTypeVComboBox;
    private JTextField tempField;
    private JTextField topPField;
    private JTextField topKField;
    private JTextField minPField;
    private JTextField ctxSizeField;
    private JTextField kwargsField;
    private JTextField nglField;
    private JComboBox<String> modelComboBox;
    private JComboBox<String> mmprojComboBox;
    private JTextArea logArea;
    private JTextArea commandPreviewArea;
    private JButton browseModelsButton;
    private JButton refreshModelsButton;
    private JButton saveProfileButton;
    private JButton copyCommandButton;
    private JButton runCommandButton;
    private JButton deleteProfileButton;
    private JComboBox<String> profileComboBox;
    private String modelsDirPath = System.getProperty("user.home") + "/models";

    public Main() {
        configLocation = new ConfigLocation();
        appConfig = new AppConfig(configLocation);
        profileManager = new ProfileManager(configLocation);
        commandBuilder = new CommandBuilder();

        setTitle("Llama Runner");
        setSize(appConfig.getWindowWidth(), appConfig.getWindowHeight());
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

        // Set application icon
        try {
            Image icon = ImageIO.read(getClass().getResource("/llama.png"));
            setIconImage(icon);
        } catch (Exception e) {
            // Icon loading failed, use default
        }

        // Set window position if not centered
        if (appConfig.getWindowX() >= 0 && appConfig.getWindowY() >= 0) {
            setLocation(appConfig.getWindowX(), appConfig.getWindowY());
        } else {
            setLocationRelativeTo(null);
        }

        JPanel mainPanel = new JPanel(new BorderLayout(8, 8));
        mainPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));

        // Top panel with model and profiles
        mainPanel.add(createTopPanel(), BorderLayout.NORTH);

        // Center container - holds config panels and command preview
        JPanel centerPanel = new JPanel(new BorderLayout(8, 8));

        // Configuration panels
        centerPanel.add(createConfigPanels(), BorderLayout.CENTER);

        // Command preview panel
        centerPanel.add(createCommandPreviewPanel(), BorderLayout.SOUTH);

        // Log panel
        mainPanel.add(centerPanel, BorderLayout.CENTER);

        // Log panel (bottom)
        mainPanel.add(createLogPanel(), BorderLayout.SOUTH);

        setContentPane(mainPanel);

        // Add window listener to save configuration on close
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                String profile = (String) profileComboBox.getSelectedItem();
                if (profile != null) {
                    appConfig.setLastProfile(profile);
                }
                saveWindowConfig();
                dispose();
                System.exit(0);
            }
        });

        // Load data after GUI is fully initialized
        SwingUtilities.invokeLater(() -> {
            loadModels();
            loadProfiles();
            if (appConfig.getLastProfile() != null && !appConfig.getLastProfile().isEmpty()) {
                profileComboBox.setSelectedItem(appConfig.getLastProfile());
            } else if (profileComboBox.getItemCount() > 0) {
                profileComboBox.setSelectedIndex(0);
            }
            if (profileComboBox.getSelectedItem() != null) {
                showLoadProfileDialog();
            }
            updateCommandPreview();
        });
    }

    private JPanel createTopPanel() {
        JPanel panel = new JPanel(new BorderLayout(10, 10));

        // Profile management panel
        panel.add(createProfileManagementPanel(), BorderLayout.NORTH);

        // Model selection panel
        panel.add(createModelSelectionPanel(), BorderLayout.SOUTH);

        return panel;
    }

    private JPanel createModelSelectionPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Model"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(0, 0, 0, 0);
        gbc.anchor = GridBagConstraints.WEST;

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.weightx = 0;
        gbc.fill = GridBagConstraints.NONE;
        panel.add(new JLabel("Model:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        modelComboBox = new JComboBox<>();
        modelComboBox.setEditable(true);
        modelComboBox.setMinimumSize(new Dimension(200, 25));
        modelComboBox.setPreferredSize(new Dimension(500, 25));
        increaseComboBoxHeight(modelComboBox);
        modelComboBox.addActionListener(e -> updateCommandPreview());
        panel.add(modelComboBox, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 0;
        gbc.fill = GridBagConstraints.NONE;
        panel.add(new JLabel("MMProj:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        mmprojComboBox = new JComboBox<>();
        mmprojComboBox.setEditable(true);
        mmprojComboBox.setMinimumSize(new Dimension(200, 25));
        mmprojComboBox.setPreferredSize(new Dimension(500, 25));
        increaseComboBoxHeight(mmprojComboBox);
        mmprojComboBox.addActionListener(e -> updateCommandPreview());
        panel.add(mmprojComboBox, gbc);

        // Buttons panel
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
        browseModelsButton = new JButton("Browse");
        browseModelsButton.addActionListener(e -> browseModelsDir());
        buttonPanel.add(browseModelsButton);

        refreshModelsButton = new JButton("Refresh");
        refreshModelsButton.addActionListener(e -> loadModels());
        buttonPanel.add(refreshModelsButton);

        gbc.gridx = 2;
        gbc.gridy = 0;
        gbc.gridheight = 2;
        gbc.weightx = 0;
        gbc.fill = GridBagConstraints.NONE;
        panel.add(buttonPanel, gbc);

        return panel;
    }

    private JPanel createProfileManagementPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Profile"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(0, 0, 0, 0);
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.NONE;

        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(new JLabel("Profile:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        profileComboBox = new JComboBox<>();
        profileComboBox.setEditable(true);
        profileComboBox.setMinimumSize(new Dimension(200, 25));
        profileComboBox.setPreferredSize(new Dimension(500, 25));
        increaseComboBoxHeight(profileComboBox);
        profileChangeListener = e -> profileComboBoxChanged();
        profileComboBox.addActionListener(profileChangeListener);
        panel.add(profileComboBox, gbc);

        // Buttons container
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));

        saveProfileButton = new JButton("Save");
        saveProfileButton.addActionListener(e -> showSaveProfileDialog());
        buttonPanel.add(saveProfileButton);

        JButton saveAsButton = new JButton("Save As...");
        saveAsButton.addActionListener(e -> showSaveAsProfileDialog());
        buttonPanel.add(saveAsButton);

        deleteProfileButton = new JButton("Delete");
        deleteProfileButton.addActionListener(e -> showDeleteProfileDialog());
        buttonPanel.add(deleteProfileButton);

        JButton renameButton = new JButton("Rename");
        renameButton.addActionListener(e -> showRenameProfileDialog());
        buttonPanel.add(renameButton);

        gbc.gridx = 2;
        gbc.weightx = 0;
        gbc.fill = GridBagConstraints.NONE;
        panel.add(buttonPanel, gbc);

        return panel;
    }

    private void loadModels() {
        String selectedModel = (String) modelComboBox.getSelectedItem();
        String selectedMmproj = (String) mmprojComboBox.getSelectedItem();

        modelComboBox.removeAllItems();
        mmprojComboBox.removeAllItems();
        mmprojComboBox.addItem("");

        File modelsDir = new File(modelsDirPath);
        if (modelsDir.exists() && modelsDir.isDirectory()) {
            File[] modelFiles = modelsDir.listFiles((dir, name) -> name.endsWith(".gguf"));
            if (modelFiles != null) {
                List<File> sortedFiles = new ArrayList<>(Arrays.asList(modelFiles));
                sortedFiles.sort((a, b) -> a.getName().compareToIgnoreCase(b.getName()));
                for (File file : sortedFiles) {
                    String displayName = formatDisplayName(file);
                    modelComboBox.addItem(displayName);
                }
            }

            File[] mmprojFiles = modelsDir.listFiles((dir, name) -> name.endsWith(".mmproj"));
            if (mmprojFiles != null) {
                List<File> sortedFiles = new ArrayList<>(Arrays.asList(mmprojFiles));
                sortedFiles.sort((a, b) -> a.getName().compareToIgnoreCase(b.getName()));
                for (File file : sortedFiles) {
                    mmprojComboBox.addItem(formatDisplayName(file));
                }
            }
        }

        restoreSelection(modelComboBox, selectedModel, false);
        restoreSelection(mmprojComboBox, selectedMmproj, true);
    }

    private String formatDisplayName(File file) {
        double sizeInGb = (double) file.length() / (1024 * 1024 * 1024);
        return String.format("%s (%.2f GB)", file.getName(), sizeInGb);
    }

    private void restoreSelection(JComboBox<String> comboBox, String selectedItem, boolean allowEmpty) {
        if (selectedItem == null || (allowEmpty && selectedItem.isEmpty())) {
            return;
        }

        boolean found = false;
        for (int i = 0; i < comboBox.getItemCount(); i++) {
            if (comboBox.getItemAt(i).equals(selectedItem)) {
                found = true;
                break;
            }
        }
        if (!found) {
            comboBox.addItem(selectedItem);
        }
        comboBox.setSelectedItem(selectedItem);
    }

    private String getSelectedFileName(JComboBox<String> comboBox) {
        String displayName = (String) comboBox.getSelectedItem();
        if (displayName == null) {
            return "";
        }
        if (displayName.contains(" (") && displayName.endsWith(" GB)")) {
            return displayName.substring(0, displayName.lastIndexOf(" ("));
        }
        return displayName;
    }

    private String buildSelectedPath(JComboBox<String> comboBox) {
        String fileName = getSelectedFileName(comboBox);
        return fileName != null && !fileName.isEmpty() ? new File(modelsDirPath, fileName).getAbsolutePath() : "";
    }

    private String buildDisplayNameForPath(String path) {
        String fileName = path != null ? new File(path).getName() : "";
        if (fileName.isEmpty()) {
            return "";
        }

        File file = new File(modelsDirPath, fileName);
        return file.exists() ? formatDisplayName(file) : fileName;
    }

    private void increaseComboBoxHeight(JComboBox<?> comboBox) {
        Dimension minimumSize = comboBox.getMinimumSize();
        Dimension preferredSize = comboBox.getPreferredSize();
        comboBox.setMinimumSize(new Dimension(minimumSize.width, minimumSize.height + 5));
        comboBox.setPreferredSize(new Dimension(preferredSize.width, preferredSize.height + 5));
    }

    private void loadProfiles() {
        String selectedItem = (String) profileComboBox.getSelectedItem();

        // Remove listener temporarily to prevent triggering during load
        profileComboBox.removeActionListener(profileChangeListener);

        try {
            profileComboBox.removeAllItems();
            List<String> profiles = profileManager.listProfiles();
            profiles.sort(String::compareToIgnoreCase);
            for (String name : profiles) {
                profileComboBox.addItem(name);
            }

            if (selectedItem != null) {
                profileComboBox.setSelectedItem(selectedItem);
            }

            // Re-add listener after loading
            profileComboBox.addActionListener(profileChangeListener);
        } catch (Exception e) {
            e.printStackTrace();
            // Re-add listener even on error
            profileComboBox.addActionListener(profileChangeListener);
            JOptionPane.showMessageDialog(this, "Error loading profiles: " + e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private void profileComboBoxChanged() {
        if (isProfileLoading) {
            return;
        }
        String selectedItem = (String) profileComboBox.getSelectedItem();
        if (selectedItem != null && !selectedItem.isEmpty()) {
            appConfig.setLastProfile(selectedItem);
            showLoadProfileDialog();
        }
    }

    private void browseModelsDir() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fileChooser.setDialogTitle("Select models directory");

        int result = fileChooser.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            modelsDirPath = fileChooser.getSelectedFile().getAbsolutePath();
            loadModels();
        }
    }

    private void showSaveProfileDialog() {
        String currentProfile = (String) profileComboBox.getSelectedItem();
        if (currentProfile == null || currentProfile.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select a profile first!", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        try {
            String validationError = validateCurrentConfig();
            if (validationError != null) {
                JOptionPane.showMessageDialog(this, validationError, "Validation Error",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
            saveProfileToName(currentProfile);
            loadProfiles();
            JOptionPane.showMessageDialog(this, "Profile saved!");

        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Error saving: " + e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private String validateCurrentConfig() {
        ModelConfig config = buildModelConfig();
        return ConfigValidation.validateConfig(config);
    }

    private void saveProfileToName(String name) throws IOException {
        ModelConfig config = buildModelConfig();
        profileManager.saveProfile(name, config);
    }

    private ModelConfig buildModelConfig() {
        ModelConfig config = new ModelConfig();
        config.setHost(hostField.getText());
        config.setPort(Integer.parseInt(portField.getText()));
        config.setParallel(Integer.parseInt(parallelField.getText()));
        config.setBatchSize(Integer.parseInt(batchSizeField.getText()));
        config.setThreads(Integer.parseInt(threadsField.getText()));
        config.setFlashAttention(flashAttnCheckBox.isSelected());
        config.setKvUnified(kvUnifiedCheckBox.isSelected());
        config.setFit(fitCheckBox.isSelected());
        config.setReasoning(reasoningCheckBox.isSelected());
        config.setCacheTypeK((String) cacheTypeKComboBox.getSelectedItem());
        config.setCacheTypeV((String) cacheTypeVComboBox.getSelectedItem());
        config.setTemperature(Double.parseDouble(tempField.getText()));
        config.setTopP(Double.parseDouble(topPField.getText()));
        config.setTopK(Integer.parseInt(topKField.getText()));
        config.setMinP(Double.parseDouble(minPField.getText()));
        config.setCtxSize(Integer.parseInt(ctxSizeField.getText()));
        config.setModelPath(buildSelectedPath(modelComboBox));
        config.setMmprojPath(buildSelectedPath(mmprojComboBox));
        config.setChatTemplateKwargs(kwargsField.getText());
        config.setNgl(Integer.parseInt(nglField.getText()));
        return config;
    }

    private void showLoadProfileDialog() {
        String name = (String) profileComboBox.getSelectedItem();
        if (name == null || name.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select a profile!", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        try {
            isProfileLoading = true;
            ModelConfig config = profileManager.loadProfile(name);

            hostField.setText(config.getHost());
            portField.setText(String.valueOf(config.getPort()));
            parallelField.setText(String.valueOf(config.getParallel()));
            batchSizeField.setText(String.valueOf(config.getBatchSize()));
            threadsField.setText(String.valueOf(config.getThreads()));
            flashAttnCheckBox.setSelected(config.isFlashAttention());
            kvUnifiedCheckBox.setSelected(config.isKvUnified());
            cacheTypeKComboBox.setSelectedItem(config.getCacheTypeK());
            cacheTypeVComboBox.setSelectedItem(config.getCacheTypeV());
            tempField.setText(String.valueOf(config.getTemperature()));
            topPField.setText(String.valueOf(config.getTopP()));
            topKField.setText(String.valueOf(config.getTopK()));
            minPField.setText(String.valueOf(config.getMinP()));
            ctxSizeField.setText(String.valueOf(config.getCtxSize()));
            modelComboBox.setSelectedItem(buildDisplayNameForPath(config.getModelPath()));
            mmprojComboBox.setSelectedItem(buildDisplayNameForPath(config.getMmprojPath()));

            kwargsField.setText(config.getChatTemplateKwargs());
            setNglFieldText(String.valueOf(config.getNgl()));
            fitCheckBox.setSelected(config.isFit());
            reasoningCheckBox.setSelected(config.isReasoning());
            toggleFitMode();

        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Error loading: " + e.getMessage() + "\n" + e.toString(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        } finally {
            isProfileLoading = false;
            // Refresh preview after all profile values are applied.
            updateCommandPreview();
        }
    }

    private void showSaveAsProfileDialog() {
        String currentProfile = (String) profileComboBox.getSelectedItem();
        String defaultName = (currentProfile != null && !currentProfile.isEmpty()) ? currentProfile : "";

        if (defaultName.isEmpty()) {
            String modelName = getSelectedFileName(modelComboBox);
            defaultName = modelName != null ? modelName : "";
        }

        String newName = InputDialog.showInputDialog(this, "Save profile as...", "Profile name:", defaultName);

        if (newName == null) {
            return; // Cancelled by user
        }

        try {
            // Validate profile name
            String validationError = ProfileValidator.validateProfileName(newName);
            if (validationError != null) {
                JOptionPane.showMessageDialog(this, validationError, "Invalid profile name",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }

            // Check if profile already exists
            if (profileManager.profileExists(newName)) {
                int confirmResult = JOptionPane.showConfirmDialog(this,
                        "Profile \"" + newName + "\" already exists. Overwrite it?",
                        "Save as...",
                        JOptionPane.YES_NO_CANCEL_OPTION,
                        JOptionPane.WARNING_MESSAGE);

                if (confirmResult != JOptionPane.YES_OPTION) {
                    return;
                }
            }

            saveProfileToName(newName);
            loadProfiles();
            profileComboBox.setSelectedItem(newName);
            JOptionPane.showMessageDialog(this, "Profile saved!");

        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Error saving: " + e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private void showDeleteProfileDialog() {
        String name = (String) profileComboBox.getSelectedItem();
        if (name == null || name.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select a profile!", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        int result = JOptionPane.showConfirmDialog(this,
                "Are you sure you want to delete profile \"" + name + "\"?",
                "Delete profile", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

        if (result != JOptionPane.YES_OPTION) {
            return;
        }

        try {
            if (!profileManager.profileExists(name)) {
                JOptionPane.showMessageDialog(this, "Profile does not exist!", "Error", JOptionPane.ERROR_MESSAGE);
                return;
            }

            profileManager.deleteProfile(name);
            loadProfiles();
            JOptionPane.showMessageDialog(this, "Profile was deleted.");
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Error deleting: " + e.getMessage() + "\n" + e.toString(),
                    "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void showRenameProfileDialog() {
        String oldName = (String) profileComboBox.getSelectedItem();
        if (oldName == null || oldName.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select a profile!", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        String newName = InputDialog.showInputDialog(this, "Rename profile", "New profile name:", oldName);

        if (newName == null) {
            return; // Cancelled by user
        }

        try {
            // Validate new profile name
            String validationError = ProfileValidator.validateProfileName(newName);
            if (validationError != null) {
                JOptionPane.showMessageDialog(this, validationError, "Invalid profile name",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }

            // Check if new name already exists
            if (profileManager.profileExists(newName)) {
                JOptionPane.showMessageDialog(this, "Profile already exists: " + newName, "Error",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }

            profileManager.renameProfile(oldName, newName);
            loadProfiles();
            profileComboBox.setSelectedItem(newName);
            JOptionPane.showMessageDialog(this, "Profile renamed to \"" + newName + "\".");
        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(this, "Error renaming: " + e.getMessage(), "Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private JPanel createConfigPanels() {
        JPanel mainPanel = new JPanel(new BorderLayout());
        mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        // Create a scrollable panel for the configuration panels
        JPanel configPanel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.fill = GridBagConstraints.HORIZONTAL;

        // Performance panel
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = 2;
        gbc.weighty = 1.0;
        configPanel.add(createPerformancePanel(), gbc);

        // Sampling panel
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.gridwidth = 2;
        configPanel.add(createSamplingPanel(), gbc);

        // Connection panel
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        gbc.weightx = 1.0;
        configPanel.add(createConnectionPanel(), gbc);

        JScrollPane scrollPane = new JScrollPane(configPanel);
        scrollPane.setBorder(null);
        scrollPane.setViewportView(configPanel);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        mainPanel.add(scrollPane, BorderLayout.CENTER);

        return mainPanel;
    }

    private JPanel createConnectionPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Connection"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(4, 4, 4, 4);
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.HORIZONTAL;

        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(new JLabel("Host:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        hostField = new JTextField("0.0.0.0", 15);
        hostField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(hostField);
        panel.add(hostField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 0;
        panel.add(new JLabel("Port:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        portField = new JTextField("3080", 10);
        portField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(portField);
        panel.add(portField, gbc);

        return panel;
    }

    private JPanel createPerformancePanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Performance"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(3, 4, 3, 4);
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.HORIZONTAL;

        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(new JLabel("Parallel:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        parallelField = new JTextField("1", 10);
        parallelField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(parallelField);
        panel.add(parallelField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 0;
        panel.add(new JLabel("Batch Size:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        batchSizeField = new JTextField("4096", 10);
        batchSizeField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(batchSizeField);
        panel.add(batchSizeField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.weightx = 0;
        panel.add(new JLabel("Threads:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        threadsField = new JTextField("99", 10);
        threadsField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(threadsField);
        panel.add(threadsField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.weightx = 0;
        panel.add(new JLabel(), gbc);

        gbc.gridx = 1;
        gbc.gridy = 3;
        gbc.weightx = 1.0;
        panel.add(new JLabel(), gbc);

        gbc.gridx = 0;
        gbc.gridy = 4;
        gbc.weightx = 0;
        flashAttnCheckBox = new JCheckBox("Flash Attention", true);
        flashAttnCheckBox.addActionListener(e -> updateCommandPreview());
        panel.add(flashAttnCheckBox, gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        panel.add(new JLabel(), gbc);

        gbc.gridx = 0;
        gbc.gridy = 5;
        gbc.weightx = 0;
        kvUnifiedCheckBox = new JCheckBox("KV Unified", true);
        kvUnifiedCheckBox.addActionListener(e -> updateCommandPreview());
        panel.add(kvUnifiedCheckBox, gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        panel.add(new JLabel(), gbc);

        gbc.gridx = 0;
        gbc.gridy = 6;
        gbc.weightx = 0;
        panel.add(new JLabel("Cache K:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        cacheTypeKComboBox = new JComboBox<>(new String[] { "bf16", "f16", "f32", "f8", "q8_0", "q5_0", "turbo3", "turbo4" });
        cacheTypeKComboBox.setSelectedIndex(4);
        increaseComboBoxHeight(cacheTypeKComboBox);
        cacheTypeKComboBox.addActionListener(e -> updateCommandPreview());
        panel.add(cacheTypeKComboBox, gbc);

        gbc.gridx = 0;
        gbc.gridy = 7;
        gbc.weightx = 0;
        panel.add(new JLabel("Cache V:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        cacheTypeVComboBox = new JComboBox<>(new String[] { "bf16", "f16", "f32", "f8", "q8_0", "q5_0", "turbo3", "turbo4" });
        cacheTypeVComboBox.setSelectedIndex(4);
        increaseComboBoxHeight(cacheTypeVComboBox);
        cacheTypeVComboBox.addActionListener(e -> updateCommandPreview());
        panel.add(cacheTypeVComboBox, gbc);

        gbc.gridx = 0;
        gbc.gridy = 8;
        gbc.weightx = 0;
        panel.add(new JLabel("GPU Layers:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        nglField = new JTextField("-1", 10);
        nglField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(nglField);
        panel.add(nglField, gbc);

        // Fit parameter
        gbc.gridx = 0;
        gbc.gridy = 9;
        gbc.weightx = 0;
        panel.add(new JLabel("Fit:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        fitCheckBox = new JCheckBox("Fit", false);
        fitCheckBox.addActionListener(e -> toggleFitMode());
        panel.add(fitCheckBox, gbc);

        gbc.gridx = 0;
        gbc.gridy = 10;
        gbc.weightx = 0;
        panel.add(new JLabel("Reasoning:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        reasoningCheckBox = new JCheckBox("Reasoning", false);
        reasoningCheckBox.addActionListener(e -> updateCommandPreview());
        panel.add(reasoningCheckBox, gbc);

        return panel;
    }

    private JPanel createSamplingPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Sampling"));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(3, 4, 3, 4);
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.HORIZONTAL;

        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(new JLabel("Temperature:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        tempField = new JTextField("0.6", 10);
        tempField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(tempField);
        panel.add(tempField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.weightx = 0;
        panel.add(new JLabel("Top P:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        topPField = new JTextField("0.95", 10);
        topPField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(topPField);
        panel.add(topPField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.weightx = 0;
        panel.add(new JLabel("Top K:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        topKField = new JTextField("20", 10);
        topKField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(topKField);
        panel.add(topKField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.weightx = 0;
        panel.add(new JLabel("Min P:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        minPField = new JTextField("0.00", 10);
        minPField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(minPField);
        panel.add(minPField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 4;
        gbc.weightx = 0;
        panel.add(new JLabel("Context Size:"), gbc);

        gbc.gridx = 1;
        gbc.weightx = 1.0;
        ctxSizeField = new JTextField("180000", 10);
        ctxSizeField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(ctxSizeField);
        panel.add(ctxSizeField, gbc);

        gbc.gridx = 0;
        gbc.gridy = 6;
        gbc.weightx = 0;
        panel.add(new JLabel("Kwargs:"), gbc);

        gbc.gridx = 1;
        gbc.gridy = 6;
        gbc.weightx = 1.0;
        kwargsField = new JTextField("{\"enable_thinking\": true}");
        kwargsField.setCaretColor(Color.WHITE);
        addFocusUpdateListener(kwargsField);
        panel.add(kwargsField, gbc);

        return panel;
    }

    private JPanel createCommandPreviewPanel() {
        JPanel panel = new JPanel(new BorderLayout(4, 4));
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Resulting command"));

        commandPreviewArea = new JTextArea();
        commandPreviewArea.setEditable(false);
        commandPreviewArea.setFont(new Font("Monospaced", Font.PLAIN, 11));
        commandPreviewArea.setLineWrap(true);
        commandPreviewArea.setWrapStyleWord(true);
        commandPreviewArea.setBackground(new Color(240, 240, 240));
        commandPreviewArea.setForeground(Color.BLACK);

        JScrollPane scrollPane = new JScrollPane(commandPreviewArea);
        scrollPane.setPreferredSize(new Dimension(0, 60));
        panel.add(scrollPane, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0));
        copyCommandButton = new JButton("Copy");
        copyCommandButton.addActionListener(e -> copyCommandToClipboard());
        buttonPanel.add(copyCommandButton);

        runCommandButton = new JButton("Run");
        runCommandButton.addActionListener(e -> runCommand());
        buttonPanel.add(runCommandButton);

        panel.add(buttonPanel, BorderLayout.EAST);

        return panel;
    }

    private void toggleFitMode() {
        nglField.setEnabled(!fitCheckBox.isSelected());
        updateCommandPreview();
    }

    private void updateCommandPreview() {
        if (isProfileLoading) {
            return;
        }
        try {
            String error = validateCurrentConfig();
            if (error != null) {
                commandPreviewArea.setText(error);
                return;
            }
            String command = buildCommand();
            commandPreviewArea.setText(command);
        } catch (NumberFormatException e) {
            commandPreviewArea.setText("Please enter valid numeric values.");
        }
    }

    private void setNglFieldText(String text) {
        nglField.setText(text);
        updateCommandPreview();
    }

    private void addFocusUpdateListener(JTextField field) {
        field.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                updateCommandPreview();
            }
        });
    }

    private void copyCommandToClipboard() {
        String command = buildCommand();
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(command), null);
    }

    private void runCommand() {
        String command = buildCommand();
        logArea.setText("========================================\n");
        logArea.append("Command executed:\n");
        logArea.append(command + "\n");
        logArea.append("========================================\n\n");

        // Use x-terminal-emulator for better compatibility
        String terminalCommand = "gnome-terminal -- bash -ic '" + command + "'";

        logArea.append("Terminal command: " + terminalCommand + "\n");

        try {
            Runtime.getRuntime().exec(new String[] { "bash", "-c", terminalCommand });

            logArea.append("Command executed in terminal.\n");

        } catch (IOException e) {
            e.printStackTrace();
            logArea.append("Error executing command: " + e.getMessage() + "\n");
        }
    }

    private JPanel createLogPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(Color.WHITE), "Log"));

        logArea = new JTextArea();
        logArea.setEditable(false);
        logArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
        logArea.setRows(10);

        JScrollPane scrollPane = new JScrollPane(logArea);
        scrollPane.setPreferredSize(new Dimension(0, 150));
        panel.add(scrollPane, BorderLayout.CENTER);

        return panel;
    }

    private String buildCommand() {
        return commandBuilder.buildCommand(buildModelConfig(), modelsDirPath);
    }

    private void saveWindowConfig() {
        appConfig.setWindowWidth(getWidth());
        appConfig.setWindowHeight(getHeight());

        // Only save position if window is not maximized
        if (getExtendedState() == 0) {
            Point location = getLocation();
            appConfig.setWindowX((int) location.getX());
            appConfig.setWindowY((int) location.getY());
        }

        appConfig.save();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e) {
                e.printStackTrace();
            }
            new Main().setVisible(true);
        });
    }
}
