"""Graphical user interface for llmserver."""

from __future__ import annotations

import atexit
import json
import logging
import os
import pathlib
import sys
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Callable, ClassVar, Optional

from PySide2.QtCore import QProcess, Qt, QTextStream, QUrl
from PySide2.QtGui import (
    QBrush,
    QColor,
    QDesktopServices,
    QFont,
    QMoveEvent,
    QResizeEvent,
    QTextCharFormat,
    QTextCursor,
)
from PySide2.QtWidgets import (
    QApplication,
    QComboBox,
    QFileDialog,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

CONFIG_FILE = Path.home() / ".pytola" / "llmserver.json"
logging.basicConfig(level="INFO", format="%(message)s")

# Style constants for consistent UI design
_UI_CONSTANTS = {
    # Dimensions - Ultra compact sizing
    "BUTTON_HEIGHT": 24,
    "INPUT_HEIGHT": 24,
    "COMPACT_SPACING": 6,
    "TIGHT_SPACING": 2,
    "MIN_MARGIN": 4,
    "SMALL_MARGIN": 8,
    "MEDIUM_MARGIN": 12,
    "LARGE_MARGIN": 16,
    # Widths - Compact dimensions
    "BUTTON_WIDTH_LARGE": 120,
    "BUTTON_WIDTH_MEDIUM": 100,
    "BUTTON_WIDTH_SMALL": 80,
    "INPUT_WIDTH_MIN": 280,
    "RESULT_WIDTH": 500,
    "RESULT_HEIGHT": 160,
    # Fonts - Even larger font sizes for excellent readability
    "FONT_SIZE_LARGE": 18,
    "FONT_SIZE_MEDIUM": 17,
    "FONT_SIZE_SMALL": 15,
    "FONT_FAMILY": "'Microsoft YaHei UI', 'Segoe UI', 'PingFang SC', sans-serif",
    "MONO_FONT": "'Consolas', 'Microsoft YaHei UI', monospace",
    # Colors (aligned with checksum style)
    "PRIMARY_BLUE": "#3b82f6",
    "PRIMARY_DARK": "#1d4ed8",
    "BORDER_COLOR": "#cbd5e1",
    "BACKGROUND_LIGHT": "#ffffff",
    "BACKGROUND_CARD": "#f8fafc",
    "TEXT_PRIMARY": "#0f172a",
    "TEXT_SECONDARY": "#64748b",
}


# Layout factory methods for consistent configuration
class LayoutFactory:
    """Factory class for creating consistent layouts and widgets."""

    @staticmethod
    def create_compact_layout(
        spacing: int = _UI_CONSTANTS["COMPACT_SPACING"],
        margins: tuple = (_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
    ) -> QHBoxLayout:
        """Create horizontally compact layout."""
        layout = QHBoxLayout()
        layout.setSpacing(spacing)
        layout.setContentsMargins(*margins)
        return layout

    @staticmethod
    def create_vertical_compact_layout(
        spacing: int = _UI_CONSTANTS["COMPACT_SPACING"],
        margins: tuple = (_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
    ) -> QVBoxLayout:
        """Create vertically compact layout."""
        layout = QVBoxLayout()
        layout.setSpacing(spacing)
        layout.setContentsMargins(*margins)
        return layout

    @staticmethod
    def configure_compact_widget(
        widget: QWidget,
        spacing: int = _UI_CONSTANTS["TIGHT_SPACING"],
        margins: tuple = (
            0,
            _UI_CONSTANTS["SMALL_MARGIN"],
            0,
            _UI_CONSTANTS["SMALL_MARGIN"],
        ),
    ) -> None:
        """Configure widget with compact layout settings."""
        if hasattr(widget, "layout") and widget.layout():
            layout = widget.layout()
            layout.setSpacing(spacing)
            layout.setContentsMargins(*margins)


# Modern unified stylesheet for llmserver module - aligned with checksum style
_LLM_SERVER_STYLESHEET = """
/* Main window and global styles - Modern design aligned with checksum */
QMainWindow, QDialog {
    background-color: #f8fafc;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', 'PingFang SC', sans-serif;
    font-size: 18px;
}

QWidget {
    font-family: 'Microsoft YaHei UI', 'Segoe UI', 'PingFang SC', sans-serif;
    font-size: 18px;
}

/* Main dialog styling with modern card design */
LlamaServerGUI {
    background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                                    stop:0 #ffffff, stop:1 #f1f5f9);
    border: 1px solid #e2e8f0;
    border-radius: 16px;
    color: #0f172a;
}

LlamaServerGUI::title {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #1d4ed8);
    color: white;
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    border-bottom: 2px solid #1e40af;
    font-size: 20px;
    font-weight: 600;
    padding: 14px 22px;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}

/* Modern group box styling with unified blue theme */
QGroupBox {
    background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                                    stop:0 #ffffff, stop:1 #f8fafc);
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    margin: 4px;
    padding-top: 20px;
    font-weight: 500;
}

QGroupBox::title {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #1d4ed8);
    color: white;
    border-top-left-radius: 6px;
    border-top-right-radius: 6px;
    border-bottom: 2px solid #1e40af;
    padding: 4px 10px;
    font-size: 17px;
    font-weight: 600;
}

/* Unified button styling with modern hover effects - aligned with checksum */
QPushButton {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #3b82f6, stop:1 #2563eb);
    color: white;
    border: none;
    border-radius: 6px;
    padding: 8px 16px;
    font-size: 17px;
    font-weight: 500;
    min-height: 32px;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
}

QPushButton:hover {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #2563eb, stop:1 #1d4ed8);
}

QPushButton:pressed {
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 #1d4ed8, stop:1 #1e40af);
}

QPushButton:disabled {
    background: #94a3b8;
    color: #cbd5e1;
}

/* Input controls with modern styling - aligned with checksum */
QLineEdit, QTextEdit {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 6px;
    padding: 8px 12px;
    font-size: 18px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    selection-background-color: #dbeafe;
    min-height: 24px;
}

QLineEdit:focus, QTextEdit:focus {
    border: 2px solid #3b82f6;
    outline: none;
}

/* SpinBox styling */
QSpinBox {
    background-color: #ffffff;
    border: 2px solid #cbd5e1;
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 17px;
    font-family: 'Consolas', 'Microsoft YaHei UI', monospace;
    min-height: 24px;
}

QSpinBox:hover {
    border: 2px solid #94a3b8;
}

QSpinBox:focus {
    border: 2px solid #3b82f6;
}

QSpinBox::up-button, QSpinBox::down-button {
    border: none;
    background: transparent;
    width: 20px;
}

/* Labels with modern typography */
QLabel {
    font-size: 18px;
    color: #334155;
    font-family: 'Microsoft YaHei UI', 'Segoe UI', sans-serif;
}

/* Scrollbar styling */
QScrollBar:vertical {
    border: none;
    background-color: #f1f5f9;
    width: 12px;
    border-radius: 6px;
    margin: 2px;
}

QScrollBar::handle:vertical {
    background-color: #94a3b8;
    border-radius: 6px;
    min-height: 20px;
}

QScrollBar::handle:vertical:hover {
    background-color: #64748b;
}

/* Status and result areas with specialized styling */
#output_area {
    background-color: #f1f5f9;
    border: 2px solid #cbd5e1;
    border-radius: 8px;
    font-family: 'Consolas', 'Courier New', monospace;
    font-size: 18px;
    color: #334155;
    padding: 12px 16px;
}

#output_area:focus {
    border: 2px solid #3b82f6;
    background-color: #e2e8f0;
}

# Object name specific styling for enhanced visual hierarchy */
#configGroup::title {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #3b82f6, stop:1 #1d4ed8);
}

#controlGroup::title {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #8b5cf6, stop:1 #7c3aed);
}

#outputGroup::title {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 #10b981, stop:1 #059669);
}
"""

# Multi-language support


class Language(Enum):
    """Supported languages."""

    CHINESE = "zh"
    ENGLISH = "en"


class TranslationManager:
    """Manage UI translations for LLM server."""

    def __init__(self) -> None:
        """Initialize translation manager with default Chinese."""
        self.current_language = Language.CHINESE
        self.translations = self._load_translations()

    def _load_translations(self) -> dict[str, dict[str, str]]:
        """Load translation dictionaries."""
        return {
            Language.CHINESE.value: {
                # Window title
                "window_title": "Llama本地模型服务器 v1.0",
                # Language selector
                "language": "语言",
                # Group box titles
                "server_config": "服务器配置",
                "server_control": "服务器控制",
                "server_output": "服务器输出",
                # Labels
                "model_path": "模型路径",
                "port": "端口",
                "threads": "线程数",
                # Buttons
                "browse": "浏览",
                "start_server": "启动服务器",
                "stop_server": "停止服务器",
                "open_browser": "打开浏览器",
                # Messages
                "choose_model_file": "选择模型文件...",
                "server_started": "服务器已启动",
                "server_stopped": "服务器已停止",
                "server_output_placeholder": "服务器输出将显示在这里...",
                "warning_model_not_found": "警告: 之前选择的模型文件未找到: {path}",
                "select_gguf_model": "选择GGUF模型文件",
                "gguf_models": "GGUF模型 (*.gguf)",
                "all_files": "所有文件 (*.*)",
                "model_selected": "模型已选择: {path}",
                "shutting_down_server": "正在关闭服务器...",
                "server_shutdown_complete": "服务器关闭完成",
                "non_zero_exit": ("注意: 非零退出代码可能表示错误。请检查上方输出了解详情。"),
            },
            Language.ENGLISH.value: {
                # Window title
                "window_title": "Llama Local Model Server v1.0",
                # Language selector
                "language": "Language",
                # Group box titles
                "server_config": "Server Configuration",
                "server_control": "Server Control",
                "server_output": "Server Output",
                # Labels
                "model_path": "Model Path",
                "port": "Port",
                "threads": "Threads",
                # Buttons
                "browse": "Browse",
                "start_server": "Start Server",
                "stop_server": "Stop Server",
                "open_browser": "Open Browser",
                # Messages
                "choose_model_file": "Choose model file...",
                "server_started": "Server started",
                "server_stopped": "Server stopped",
                "server_output_placeholder": "Server output will appear here...",
                "warning_model_not_found": ("Warning: Previously selected model file not found: {path}"),
                "select_gguf_model": "Select GGUF Model File",
                "gguf_models": "GGUF Models (*.gguf)",
                "all_files": "All Files (*)",
                "model_selected": "Model selected: {path}",
                "shutting_down_server": "Shutting down server...",
                "server_shutdown_complete": "Server shutdown complete",
                "non_zero_exit": ("Note: Non-zero exit code may indicate an error. Check output above for details."),
            },
        }

    def set_language(self, language: Language) -> None:
        """Set current language."""
        self.current_language = language

    def tr(self, key: str, **kwargs) -> str:
        """Translate key to current language."""
        translation = self.translations[self.current_language.value].get(key, key)
        if kwargs:
            translation = translation.format(**kwargs)
        return translation

    def get_available_languages(self) -> list[tuple[str, str]]:
        """Get available languages for UI display."""
        return [
            (Language.CHINESE.value, "中文"),
            (Language.ENGLISH.value, "English"),
        ]


# Global translation manager
_translation_manager = TranslationManager()

logger = logging.getLogger(__name__)


@dataclass
class LLMServerConfig:
    """Llama local model server configuration with persistent settings."""

    # Application metadata
    TITLE: ClassVar[str] = "Llama Local Model Server"

    # Window configuration - ClassVar as these are application defaults
    WIN_SIZE: ClassVar[list[int]] = [600, 500]
    WIN_POS: ClassVar[list[int]] = [200, 200]

    # Server configuration
    MODEL_PATH: str = ""
    URL: str = "http://127.0.0.1"
    LISTEN_PORT: int = 8080
    LISTEN_PORT_RNG: ClassVar[list[int]] = [1024, 65535]
    THREAD_COUNT: int = 4
    THREAD_COUNT_RNG: ClassVar[list[int]] = [1, 24]

    # Internal state
    _loaded_from_file: bool = False
    _explicitly_set: set[str] = None  # Track fields set via constructor

    def __post_init__(self) -> None:
        """Initialize configuration and load from file if exists."""
        self._load_from_file()

    def __init__(
        self,
        MODEL_PATH: str = "",
        URL: str = "http://127.0.0.1",
        LISTEN_PORT: int = 8080,
        THREAD_COUNT: int = 4,
        WIN_POS: Optional[list[int]] = None,
        WIN_SIZE: Optional[list[int]] = None,
    ) -> None:
        """Initialize configuration with optional parameters and load from file."""
        # Track which fields are explicitly set via constructor
        self._explicitly_set = set()

        # Set initial values and track explicitly set fields
        if MODEL_PATH != "":  # Default empty string
            self.MODEL_PATH = MODEL_PATH
            self._explicitly_set.add("MODEL_PATH")

        if URL != "http://127.0.0.1":  # Default value
            self.URL = URL
            self._explicitly_set.add("URL")

        if LISTEN_PORT != 8080:  # Default value
            self.LISTEN_PORT = LISTEN_PORT
            self._explicitly_set.add("LISTEN_PORT")

        if THREAD_COUNT != 4:  # Default value
            self.THREAD_COUNT = THREAD_COUNT
            self._explicitly_set.add("THREAD_COUNT")

        # Handle window position and size (now ClassVar, so we store in instance)
        if WIN_POS is not None and len(WIN_POS) == 2:
            self.WIN_POS = WIN_POS
            self._explicitly_set.add("WIN_POS")
        else:
            self.WIN_POS = [200, 200]  # Default position

        if WIN_SIZE is not None and len(WIN_SIZE) == 2:
            self.WIN_SIZE = WIN_SIZE
            self._explicitly_set.add("WIN_SIZE")
        else:
            self.WIN_SIZE = [600, 500]  # Default size

        self._loaded_from_file = False

        # Then load existing configuration from file (will override only unset fields)
        self._load_from_file()

    def _load_from_file(self) -> None:
        """Load configuration from file, preserving defaults for missing values."""
        if CONFIG_FILE.exists():
            logger.info("Loading configuration from %s", CONFIG_FILE)
            try:
                config_data = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
                # Import dataclasses to access _FIELD constant
                import dataclasses

                # Safely update only instance attributes that are dataclass fields
                # Only load fields that weren't explicitly set via constructor
                for key, value in config_data.items():
                    # Only update if it's a valid dataclass field
                    # (not ClassVar or private)
                    # AND the field wasn't explicitly set via constructor
                    # AND the loaded value is not empty/null
                    if (
                        key in self.__dataclass_fields__
                        and self.__dataclass_fields__[key]._field_type is dataclasses._FIELD
                        and key not in self._explicitly_set
                        and value not in (None, "")  # Don't override with empty values
                    ):
                        try:
                            setattr(self, key, value)
                        except (AttributeError, TypeError) as e:
                            logger.warning(f"Could not set {key}: {e}")
                self._loaded_from_file = True
            except (json.JSONDecodeError, TypeError, AttributeError) as e:
                logger.error(f"Error loading config from {CONFIG_FILE}: {e}")
        else:
            logger.info("Using default configuration")

    def save(self) -> None:
        """Save current configuration to file."""
        try:
            CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
            # Extract only instance attributes for serialization
            config_dict = {}

            # Fields that should not be serialized (ClassVar-like fields)
            excluded_fields = {
                "TITLE",
                "LISTEN_PORT_RNG",
                "THREAD_COUNT_RNG",
            }

            # Import dataclasses to access _FIELD constant
            import dataclasses

            for field_name, field_info in self.__dataclass_fields__.items():
                # Skip private fields and explicitly excluded fields
                if field_name.startswith("_") or field_name in excluded_fields:
                    continue

                # Only serialize regular instance fields
                if field_info._field_type == dataclasses._FIELD:
                    try:
                        attr_value = getattr(self, field_name)
                        # Only serialize non-callable attributes
                        if not callable(attr_value):
                            config_dict[field_name] = attr_value
                    except AttributeError:
                        continue

            CONFIG_FILE.write_text(json.dumps(config_dict, indent=4, ensure_ascii=False), encoding="utf-8")
        except Exception as e:
            error_msg = f"Failed to save config: {e}"
            logger.error(error_msg)
            raise RuntimeError(error_msg) from e


conf = LLMServerConfig()
atexit.register(conf.save)


class LlamaServerGUI(QMainWindow):
    """Llama local model server GUI."""

    # Color constants
    COLOR_ERROR = QColor(255, 0, 0)
    COLOR_WARNING = QColor(255, 165, 0)
    COLOR_INFO = QColor(0, 0, 0)

    # Process constants
    PROCESS_TERMINATE_TIMEOUT_MS = 2000

    def __init__(self) -> None:
        super().__init__()
        self.setWindowTitle(_translation_manager.tr("window_title"))

        # Apply window geometry from configuration
        self.setGeometry(conf.WIN_POS[0], conf.WIN_POS[1], conf.WIN_SIZE[0], conf.WIN_SIZE[1])

        self.process: QProcess

        # Apply modern styles
        self._apply_styles()

        self.init_ui()
        self.setup_process()

        # Apply loaded configuration to UI controls
        self.apply_config_to_ui()

        # Update UI text to current language
        self._update_ui_text()

    def _apply_styles(self) -> None:
        """Apply integrated QSS styles to the GUI."""
        try:
            self.setStyleSheet(_LLM_SERVER_STYLESHEET)
        except Exception as e:
            logger.warning(f"Failed to apply styles: {e}")

    def init_ui(self) -> None:
        """Initialize user interface with comfortable spacing and multi-language support."""
        # Main layout with comfortable spacing
        main_widget = QWidget()
        main_layout = LayoutFactory.create_vertical_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["MEDIUM_MARGIN"],) * 4,
        )
        self.setLayout(main_layout)

        # Language selector at the top
        language_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["TIGHT_SPACING"],
            margins=(
                _UI_CONSTANTS["MIN_MARGIN"],
                _UI_CONSTANTS["TIGHT_SPACING"],
                _UI_CONSTANTS["MIN_MARGIN"],
                _UI_CONSTANTS["TIGHT_SPACING"],
            ),
        )

        self.language_label = QLabel(_translation_manager.tr("language"))
        self.language_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_SMALL"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
            padding: (
                f"{_UI_CONSTANTS["TIGHT_SPACING"] // 2}px "
                f"{_UI_CONSTANTS["MIN_MARGIN"]}px"
            );
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            """
        )
        self.language_label.setMinimumHeight(20)
        self.language_label.setMaximumHeight(20)
        language_layout.addWidget(self.language_label)

        self.language_combo = QComboBox()
        self.language_combo.setMinimumWidth(100)
        self.language_combo.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.language_combo.setMaximumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.language_combo.setStyleSheet("padding: 2px 4px;")

        # Populate language options
        for lang_code, lang_name in _translation_manager.get_available_languages():
            self.language_combo.addItem(lang_name, lang_code)

        # Set current language
        current_lang_index = self.language_combo.findData(_translation_manager.current_language.value)
        if current_lang_index >= 0:
            self.language_combo.setCurrentIndex(current_lang_index)

        self.language_combo.currentIndexChanged.connect(self._change_language)
        language_layout.addWidget(self.language_combo)
        language_layout.addStretch()

        main_layout.addLayout(language_layout)

        # Configuration panel with ultra-compact design
        self.config_group = QGroupBox(_translation_manager.tr("server_config"))
        self.config_group.setObjectName("configGroup")
        config_layout = LayoutFactory.create_vertical_compact_layout(
            spacing=_UI_CONSTANTS["TIGHT_SPACING"],
            margins=(_UI_CONSTANTS["MIN_MARGIN"],) * 4,
        )
        self.config_group.setLayout(config_layout)

        # Model path selection section with ultra-compact spacing
        model_section = QWidget()
        model_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["TIGHT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        model_section.setLayout(model_layout)

        # Add compact label for model path
        self.model_label = QLabel(_translation_manager.tr("model_path"))
        self.model_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
            padding: (
                f"{_UI_CONSTANTS["TIGHT_SPACING"]}px "
                f"{_UI_CONSTANTS["MIN_MARGIN"]}px"
            );
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            """
        )
        self.model_label.setMinimumHeight(20)
        self.model_label.setMaximumHeight(20)
        model_layout.addWidget(self.model_label)

        self.model_path_input = QLineEdit()
        self.model_path_input.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.model_path_input.setMaximumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.model_path_input.setMinimumWidth(_UI_CONSTANTS["INPUT_WIDTH_MIN"])
        self.model_path_input.setStyleSheet("padding: 2px;")
        self.model_path_input.setAlignment(Qt.AlignLeft)
        self.model_path_input.setPlaceholderText(_translation_manager.tr("choose_model_file"))
        # Add tooltip to show full path when hovered
        self.model_path_input.setToolTip("")
        # Connect text change signal to update tooltip
        self.model_path_input.textChanged.connect(self._update_model_path_tooltip)
        self.load_model_btn = QPushButton(_translation_manager.tr("browse"))
        self.load_model_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_SMALL"])
        self.load_model_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.load_model_btn.setMaximumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.load_model_btn.setStyleSheet("padding: 2px 6px;")
        self.load_model_btn.clicked.connect(self.on_load_model)

        model_layout.addWidget(self.model_path_input, 1)
        model_layout.addWidget(self.load_model_btn)

        # Server parameters section with compact grid layout
        params_section = QWidget()
        params_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["TIGHT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        params_section.setLayout(params_layout)

        # Port configuration - compact row
        self.port_label = QLabel(_translation_manager.tr("port"))
        self.port_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
            padding: (
                f"{_UI_CONSTANTS["TIGHT_SPACING"]}px "
                f"{_UI_CONSTANTS["MIN_MARGIN"]}px"
            );
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            """
        )
        self.port_label.setMinimumHeight(20)
        self.port_label.setMaximumHeight(20)
        self.port_spin = QSpinBox()
        self.port_spin.setRange(*conf.LISTEN_PORT_RNG)
        self.port_spin.setValue(conf.LISTEN_PORT)
        self.port_spin.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_SMALL"])
        self.port_spin.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.port_spin.setMaximumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.port_spin.setStyleSheet("padding: 2px;")
        self.port_spin.valueChanged.connect(self.on_config_changed)

        params_layout.addWidget(self.port_label)
        params_layout.addWidget(self.port_spin)

        # Threads configuration - compact row
        self.threads_label = QLabel(_translation_manager.tr("threads"))
        self.threads_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
            padding: (
                f"{_UI_CONSTANTS["TIGHT_SPACING"]}px "
                f"{_UI_CONSTANTS["MIN_MARGIN"]}px"
            );
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            """
        )
        self.threads_label.setMinimumHeight(20)
        self.threads_label.setMaximumHeight(20)
        self.threads_spin = QSpinBox()
        self.threads_spin.setRange(*conf.THREAD_COUNT_RNG)
        self.threads_spin.setValue(conf.THREAD_COUNT)
        self.threads_spin.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_SMALL"])
        self.threads_spin.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.threads_spin.setMaximumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.threads_spin.setStyleSheet("padding: 2px;")
        self.threads_spin.valueChanged.connect(self.on_config_changed)

        params_layout.addWidget(self.threads_label)
        params_layout.addWidget(self.threads_spin)
        params_layout.addStretch()

        # Add sections to config layout
        config_layout.addWidget(model_section)
        config_layout.addWidget(params_section)

        # Control buttons with comfortable styling
        self.control_group = QGroupBox(_translation_manager.tr("server_control"))
        self.control_group.setObjectName("controlGroup")
        control_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(
                _UI_CONSTANTS["MEDIUM_MARGIN"],
                _UI_CONSTANTS["LARGE_MARGIN"],
                _UI_CONSTANTS["MEDIUM_MARGIN"],
                _UI_CONSTANTS["LARGE_MARGIN"],
            ),
        )
        self.control_group.setLayout(control_layout)

        self.start_btn = QPushButton(_translation_manager.tr("start_server"))
        self.start_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.start_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_LARGE"])
        self.start_btn.setStyleSheet("padding: 4px 12px;")
        self.start_btn.clicked.connect(self.toggle_server)

        self.browser_btn = QPushButton(_translation_manager.tr("open_browser"))
        self.browser_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.browser_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_LARGE"])
        self.browser_btn.setStyleSheet("padding: 4px 12px;")
        self.browser_btn.setEnabled(False)
        self.browser_btn.clicked.connect(self.on_start_browser)

        control_layout.addWidget(self.start_btn)
        control_layout.addWidget(self.browser_btn)
        control_layout.addStretch()

        # Output display - standalone area without GroupBox
        self.output_label = QLabel(_translation_manager.tr("server_output"))
        self.output_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 600;
            color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
            padding: (
                f"{_UI_CONSTANTS["TIGHT_SPACING"]}px 0px"
            );
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            """
        )

        self.output_area = QTextEdit("")
        self.output_area.setObjectName("output_area")
        self.output_area.setReadOnly(True)
        self.output_area.setLineWrapMode(QTextEdit.NoWrap)
        self.output_area.setMinimumWidth(_UI_CONSTANTS["RESULT_WIDTH"])
        self.output_area.setMinimumHeight(_UI_CONSTANTS["RESULT_HEIGHT"])
        self.output_area.setPlaceholderText(_translation_manager.tr("server_output_placeholder"))

        # Set colors for different message types
        self.error_format = self.create_text_format(self.COLOR_ERROR)
        self.warning_format = self.create_text_format(self.COLOR_WARNING)
        self.info_format = self.create_text_format(self.COLOR_INFO)

        # Assemble main layout
        main_layout.addWidget(self.config_group)
        main_layout.addWidget(self.control_group)
        main_layout.addWidget(self.output_label)
        main_layout.addWidget(self.output_area, 1)

        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

    def _change_language(self, index: int) -> None:
        """Change UI language and update all text elements."""
        # Get selected language
        lang_code = self.language_combo.itemData(index)
        if lang_code == Language.CHINESE.value:
            _translation_manager.set_language(Language.CHINESE)
        else:
            _translation_manager.set_language(Language.ENGLISH)

        # Update all UI elements
        self._update_ui_text()

    def _update_ui_text(self) -> None:
        """Update all UI text elements to current language with reduced complexity."""
        # Cache translation function for better performance
        tr = _translation_manager.tr

        # Update basic UI elements
        self._update_window_title(tr)
        self._update_language_label(tr)

        # Update group elements
        self._update_group_box_titles(tr)
        self._update_labels(tr)
        self._update_buttons(tr)
        self._update_placeholders(tr)

    def _update_window_title(self, tr: Callable[[str], str]) -> None:
        """Update window title."""
        self.setWindowTitle(tr("window_title"))

    def _update_language_label(self, tr: Callable[[str], str]) -> None:
        """Update language selector label."""
        self.language_label.setText(tr("language"))

    def _update_group_box_titles(self, tr: Callable[[str], str]) -> None:
        """Update group box titles."""
        if hasattr(self, "config_group"):
            self.config_group.setTitle(tr("server_config"))
        if hasattr(self, "control_group"):
            self.control_group.setTitle(tr("server_control"))
        if hasattr(self, "output_label"):
            self.output_label.setText(tr("server_output"))

    def _update_labels(self, tr: Callable[[str], str]) -> None:
        """Update various labels in the UI."""
        if hasattr(self, "model_label"):
            self.model_label.setText(tr("model_path"))
        if hasattr(self, "port_label"):
            self.port_label.setText(tr("port"))
        if hasattr(self, "threads_label"):
            self.threads_label.setText(tr("threads"))

    def _update_buttons(self, tr: Callable[[str], str]) -> None:
        """Update button texts with appropriate logic."""
        if hasattr(self, "load_model_btn"):
            self.load_model_btn.setText(tr("browse"))

        if hasattr(self, "start_btn"):
            self._update_start_button_text(tr)
        if hasattr(self, "browser_btn"):
            self.browser_btn.setText(tr("open_browser"))

    def _update_start_button_text(self, tr: Callable[[str], str]) -> None:
        """Update start button text based on server state."""
        if self.process and self.process.state() == QProcess.Running:
            self.start_btn.setText(tr("stop_server"))
        else:
            self.start_btn.setText(tr("start_server"))

    def _update_placeholders(self, tr: Callable[[str], str]) -> None:
        """Update placeholder texts for input fields."""
        if hasattr(self, "model_path_input"):
            self.model_path_input.setPlaceholderText(tr("choose_model_file"))
        if hasattr(self, "output_area"):
            self.output_area.setPlaceholderText(tr("server_output_placeholder"))

    @staticmethod
    def create_text_format(color: QColor) -> QTextCharFormat:
        """Create text format.

        Args:
            color: Text color.

        Returns
        -------
            Text format.
        """
        text_format = QTextCharFormat()
        text_format.setForeground(QBrush(color))
        return text_format

    def setup_process(self) -> None:
        """Initialize process."""
        self.process = QProcess(self)
        self.process.readyReadStandardOutput.connect(self.handle_stdout)  # type: ignore
        self.process.readyReadStandardError.connect(self.handle_stderr)  # type: ignore
        self.process.finished.connect(self.on_process_finished)  # type: ignore

    def apply_config_to_ui(self) -> None:
        """Apply loaded configuration to UI controls."""
        # Set model path
        model_path = conf.MODEL_PATH
        if model_path and Path(model_path).exists():
            self.model_path_input.setText(str(model_path))
        else:
            self.model_path_input.setPlaceholderText(_translation_manager.tr("choose_model_file"))
            if model_path:
                self.append_output(
                    _translation_manager.tr("warning_model_not_found", path=model_path) + "\n",
                    self.warning_format,
                )

        # Set port and thread values
        self.port_spin.setValue(conf.LISTEN_PORT)
        self.threads_spin.setValue(conf.THREAD_COUNT)

    def _update_model_path_tooltip(self, text: str) -> None:
        """Update model path input tooltip to show full path."""
        self.model_path_input.setToolTip(text)

    def on_config_changed(self) -> None:
        """Handle configuration changes and update internal state."""
        conf.MODEL_PATH = self.model_path_input.text().strip()
        conf.LISTEN_PORT = self.port_spin.value()
        conf.THREAD_COUNT = self.threads_spin.value()
        # Configuration will be saved on exit via atexit.register

    def on_load_model(self) -> None:
        """Select model file with improved file dialog."""
        initial_dir = (
            str(Path(conf.MODEL_PATH).parent)
            if conf.MODEL_PATH and Path(conf.MODEL_PATH).exists()
            else str(Path.home())
        )

        path, _ = QFileDialog.getOpenFileName(
            self,
            _translation_manager.tr("select_gguf_model"),
            initial_dir,
            f"{_translation_manager.tr('gguf_models')};;{_translation_manager.tr('all_files')}",
        )

        if path:
            conf.MODEL_PATH = path
            normalized_path = os.path.normpath(path)
            self.model_path_input.setText(normalized_path)
            # Update tooltip to show full path
            self.model_path_input.setToolTip(normalized_path)
            self.append_output(
                _translation_manager.tr("model_selected", path=path) + "\n",
                self.info_format,
            )

    def toggle_server(self) -> None:
        """Start or stop server."""
        if self.process.state() == QProcess.Running:
            self.stop_server()
        else:
            self.start_server()

    def start_server(self) -> None:
        """Start server with enhanced validation and error handling."""
        model_path = pathlib.Path(self.model_path_input.text().strip())

        # Enhanced validation
        if not model_path.exists():
            self.append_output(
                f"Error: Model file not found: {model_path}\n",
                self.error_format,
            )
            return

        if not model_path.is_file():
            self.append_output(
                f"Error: Path is not a file: {model_path}\n",
                self.error_format,
            )
            return

        if model_path.suffix.lower() != ".gguf":
            self.append_output(
                f"Warning: Model file does not have .gguf extension: {model_path}\n",
                self.warning_format,
            )

        try:
            os.chdir(str(model_path.parent))
        except OSError as e:
            self.append_output(
                f"Error: Cannot access model directory: {e}\n",
                self.error_format,
            )
            return

        cmd = [
            "llama-server",
            "--model",
            model_path.name,
            "--port",
            str(self.port_spin.value()),
            "--threads",
            str(self.threads_spin.value()),
        ]

        self.append_output(f"Starting server: {' '.join(cmd)}\n", self.info_format)
        self.append_output(f"Working directory: {model_path.parent}\n", self.info_format)

        try:
            self.process.start(cmd[0], cmd[1:])
            if self.process.waitForStarted(5000):  # Wait up to 5 seconds for startup
                self.update_ui_state(running=True)
                self.append_output(_translation_manager.tr("server_started") + "\n", self.info_format)
            else:
                self.append_output(
                    "Error: Failed to start server process\n",
                    self.error_format,
                )
        except Exception as e:
            self.append_output(f"Start failed: {e!s}\n", self.error_format)

    def stop_server(self) -> None:
        """Stop server."""
        if self.process.state() == QProcess.Running:
            self.append_output(_translation_manager.tr("shutting_down_server") + "\n", self.info_format)
            self.process.terminate()
            if not self.process.waitForFinished(self.PROCESS_TERMINATE_TIMEOUT_MS):
                self.process.kill()

    @staticmethod
    def on_start_browser() -> None:
        """Start browser."""
        QDesktopServices.openUrl(QUrl(f"{conf.URL}:{conf.LISTEN_PORT}"))

    def on_process_finished(self, exit_code: int, exit_status: int) -> None:
        """Process finished with enhanced status reporting."""
        status_msg = f"Server stopped. Exit code: {exit_code}, Status: {exit_status}"
        self.append_output(f"\n{status_msg}\n", self.info_format)

        # Provide helpful information based on exit code
        if exit_code != 0:
            self.append_output(
                _translation_manager.tr("non_zero_exit") + "\n",
                self.warning_format,
            )

        self.update_ui_state(running=False)

    def handle_stdout(self) -> None:
        """Handle standard output."""
        data = self.process.readAllStandardOutput()
        text = QTextStream(data).readAll()
        self.append_output(text, self.info_format)

    def handle_stderr(self) -> None:
        """Handle standard error."""
        data = self.process.readAllStandardError()
        text = QTextStream(data).readAll()
        self.append_output(text, self.error_format)

    def append_output(
        self,
        text: str,
        text_format: QTextCharFormat | None = None,
    ) -> None:
        """Append output."""
        cursor: QTextCursor = self.output_area.textCursor()
        cursor.movePosition(QTextCursor.MoveOperation.End)

        if text_format:
            cursor.setCharFormat(text_format)

        cursor.insertText(text)

        self.output_area.setTextCursor(cursor)
        self.output_area.ensureCursorVisible()

    def update_ui_state(self, *, running: bool) -> None:
        """Update UI state."""
        self.model_path_input.setEnabled(not running)
        self.load_model_btn.setEnabled(not running)
        self.port_spin.setEnabled(not running)
        self.threads_spin.setEnabled(not running)
        self.browser_btn.setEnabled(running)

        if running:
            self.start_btn.setText("Stop Server")
        else:
            self.start_btn.setText("Start Server")

    def moveEvent(self, event: QMoveEvent) -> None:
        """Handle window move event."""
        top_left = self.geometry().topLeft()
        conf.WIN_POS = [top_left.x(), top_left.y()]
        return super().moveEvent(event)

    def resizeEvent(self, event: QResizeEvent) -> None:
        """Handle window resize event."""
        geometry = self.geometry()
        conf.WIN_SIZE = [geometry.width(), geometry.height()]
        return super().resizeEvent(event)

    def closeEvent(self, event) -> None:
        """Handle window close event with enhanced cleanup."""
        try:
            # Save current configuration
            conf.save()
            logger.info("Configuration saved successfully on exit")
        except Exception as e:
            logger.error(f"Failed to save configuration on exit: {e}")

        try:
            # Stop server if running
            if hasattr(self, "process") and self.process.state() == QProcess.Running:
                self.append_output(
                    _translation_manager.tr("shutting_down_server") + "\n",
                    self.info_format,
                )
                self.process.terminate()
                if not self.process.waitForFinished(3000):  # Wait up to 3 seconds
                    self.process.kill()
                    self.process.waitForFinished(1000)
                self.append_output(
                    _translation_manager.tr("server_shutdown_complete") + "\n",
                    self.info_format,
                )
        except Exception as e:
            logger.error(f"Error during shutdown: {e}")
        finally:
            event.accept()


def main() -> None:
    """Run entry point for the GUI application.

    Graphical interface for llmserver
    """
    app = QApplication(sys.argv)
    app.setFont(QFont("Consolas", 12))
    window = LlamaServerGUI()
    window.show()
    sys.exit(app.exec_())
