"""LLM Chat client application.

Provides a graphical interface client for streaming conversations with LLM servers.
Supports real-time streaming response display, connection testing, and parameter adjustment.
"""

from __future__ import annotations

import atexit
import json
import logging
import sys
from codecs import getincrementaldecoder
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import ClassVar
from urllib.error import URLError
from urllib.request import Request, urlopen

from PySide2.QtCore import Qt, QThread, Signal
from PySide2.QtGui import QMoveEvent, QResizeEvent, QTextCursor
from PySide2.QtWidgets import (
    QApplication,
    QComboBox,
    QDoubleSpinBox,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

# Style constants for consistent UI design
_UI_CONSTANTS = {
    # Dimensions
    "BUTTON_HEIGHT": 32,
    "INPUT_HEIGHT": 32,
    "COMPACT_SPACING": 4,
    "TIGHT_SPACING": 2,
    "MIN_MARGIN": 2,
    "SMALL_MARGIN": 8,
    "MEDIUM_MARGIN": 12,
    "LARGE_MARGIN": 16,
    # Widths
    "BUTTON_WIDTH_LARGE": 120,
    "BUTTON_WIDTH_MEDIUM": 100,
    "BUTTON_WIDTH_SMALL": 80,
    "INPUT_WIDTH_MIN": 280,
    "RESULT_WIDTH": 500,
    "RESULT_HEIGHT": 150,
    "LANGUAGE_COMBO_WIDTH": 100,
    # Fonts
    "FONT_SIZE_LARGE": 16,
    "FONT_SIZE_MEDIUM": 15,
    "FONT_SIZE_SMALL": 12,
    "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",
    # Label styling - Simple and clean design
    "LABEL_PADDING_VERTICAL": 4,
    "LABEL_PADDING_HORIZONTAL": 8,
    "LABEL_BORDER_RADIUS": 4,
    "LABEL_BACKGROUND": "transparent",
    "LABEL_TEXT_COLOR": "#0f172a",
    "LABEL_BORDER_COLOR": "transparent",
}


class LayoutFactory:
    """Factory class for creating consistent UI layouts with compact spacing and margins."""

    @staticmethod
    def create_compact_layout(
        spacing: int = _UI_CONSTANTS["COMPACT_SPACING"],
        margins: tuple = (_UI_CONSTANTS["MIN_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["MIN_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["MIN_MARGIN"],
            0,
            _UI_CONSTANTS["MIN_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 llmclient module - aligned with checksum style
_LLMCLIENT_STYLESHEET = f"""
/* Main window and global styles - Modern design aligned with checksum */
QMainWindow {{
    background-color: {_UI_CONSTANTS["BACKGROUND_CARD"]};
    font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
    font-size: {_UI_CONSTANTS["FONT_SIZE_LARGE"]}px;
}}

QWidget {{
    font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
    font-size: {_UI_CONSTANTS["FONT_SIZE_LARGE"]}px;
}}

/* Main window styling with modern card design */
LLMChatApp {{
    background-color: qlineargradient(
        x1:0, y1:0, x2:0, y2:1,
        stop:0 {_UI_CONSTANTS["BACKGROUND_LIGHT"]},
        stop:1 {_UI_CONSTANTS["BACKGROUND_CARD"]}
    );
    border: 1px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 16px;
    color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
}}

LLMChatApp::title {{
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 {_UI_CONSTANTS["PRIMARY_BLUE"]}, stop:1 {_UI_CONSTANTS["PRIMARY_DARK"]});
    color: white;
    border-top-left-radius: 15px;
    border-top-right-radius: 15px;
    border-bottom: 2px solid #1e40af;
    font-size: 18px;
    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 {_UI_CONSTANTS["BACKGROUND_LIGHT"]},
        stop:1 {_UI_CONSTANTS["BACKGROUND_CARD"]}
    );
    border: 1px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 12px;
    margin: 8px;
    padding-top: 24px;
    font-weight: 500;
}}

QGroupBox::title {{
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                              stop:0 {_UI_CONSTANTS["PRIMARY_BLUE"]}, stop:1 {_UI_CONSTANTS["PRIMARY_DARK"]});
    color: white;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    border-bottom: 2px solid #1e40af;
    padding: 6px 14px;
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    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 {_UI_CONSTANTS["PRIMARY_BLUE"]}, stop:1 {_UI_CONSTANTS["PRIMARY_BLUE"]});
    color: white;
    border: none;
    border-radius: 8px;
    padding: 12px 20px;
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    font-weight: 500;
    min-height: {_UI_CONSTANTS["BUTTON_HEIGHT"]}px;
    font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
}}

QPushButton:hover {{
    background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                              stop:0 {_UI_CONSTANTS["PRIMARY_DARK"]}, stop:1 {_UI_CONSTANTS["PRIMARY_DARK"]});
}}

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

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

/* Input controls with modern styling - aligned with checksum */
QLineEdit, QTextEdit {{
    background-color: {_UI_CONSTANTS["BACKGROUND_LIGHT"]};
    border: 2px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 8px;
    padding: 12px 16px;
    font-size: {_UI_CONSTANTS["FONT_SIZE_LARGE"]}px;
    font-family: {_UI_CONSTANTS["MONO_FONT"]};
    selection-background-color: #dbeafe;
    min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
}}

QLineEdit:focus, QTextEdit:focus {{
    border: 2px solid {_UI_CONSTANTS["PRIMARY_BLUE"]};
    outline: none;
}}

/* SpinBox styling */
QSpinBox, QDoubleSpinBox {{
    background-color: {_UI_CONSTANTS["BACKGROUND_LIGHT"]};
    border: 2px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 8px;
    padding: 10px 14px;
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    font-family: {_UI_CONSTANTS["MONO_FONT"]};
    min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
}}

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

QSpinBox:focus, QDoubleSpinBox:focus {{
    border: 2px solid {_UI_CONSTANTS["PRIMARY_BLUE"]};
}}

/* Labels with modern typography */
QLabel {{
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
    font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
}}

/* ComboBox styling */
QComboBox {{
    background-color: {_UI_CONSTANTS["BACKGROUND_LIGHT"]};
    border: 2px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 8px;
    padding: 10px 14px;
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    font-family: {_UI_CONSTANTS["MONO_FONT"]};
    min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
}}

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

QComboBox:focus {{
    border: 2px solid {_UI_CONSTANTS["PRIMARY_BLUE"]};
}}

QComboBox::drop-down {{
    border: none;
    width: 20px;
}}

/* 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;
}}

/* Chat display area with specialized styling */
QTextEdit#chatDisplay {{
    background-color: {_UI_CONSTANTS["BACKGROUND_LIGHT"]};
    border: 2px solid {_UI_CONSTANTS["BORDER_COLOR"]};
    border-radius: 8px;
    font-family: {_UI_CONSTANTS["MONO_FONT"]};
    font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
    color: {_UI_CONSTANTS["TEXT_PRIMARY"]};
    padding: 12px 16px;
}}

QTextEdit#chatDisplay:focus {{
    border: 2px solid {_UI_CONSTANTS["PRIMARY_BLUE"]};
    background-color: #f8fafc;
}}
"""

CONFIG_FILE = Path.home() / ".pytola" / "llmclient.json"
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)

CONNECTION_TIMEOUT = 5


@dataclass
class LLMClientConfig:
    """LLM Chat client configuration with persistence support."""

    TITLE: str = "LLM Chat Client v1.0"
    WIN_SIZE: ClassVar[list[int]] = [900, 700]
    WIN_POS: ClassVar[list[int]] = [100, 100]
    SERVER_URL: str = "http://localhost:8080"
    MAX_TOKENS: int = 256
    TEMPERATURE: float = 0.7
    TOP_P: float = 0.9
    TOP_K: int = 40
    MAX_TOKENS_RANGE: ClassVar[list[int]] = [1, 4096]
    TEMPERATURE_RANGE: ClassVar[list[float]] = [0.0, 2.0]
    TOP_P_RANGE: ClassVar[list[float]] = [0.0, 1.0]
    TOP_K_RANGE: ClassVar[list[int]] = [1, 100]
    language: str = "zh"  # Default language
    _loaded_from_file: bool = False

    def __post_init__(self) -> None:
        """Initialize configuration after creation and load from file if exists."""
        if CONFIG_FILE.exists():
            logger.info("Loading configuration from %s", CONFIG_FILE)
            try:
                config_data = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
                # Update instance attributes with loaded values
                for key, value in config_data.items():
                    if hasattr(self, key) and isinstance(
                        value,
                        type(getattr(self, key)),
                    ):
                        setattr(self, key, value)
                self._loaded_from_file = True
            except (json.JSONDecodeError, TypeError, AttributeError) as e:
                logger.warning("Failed to load configuration: %s", e)
                logger.info("Using default configuration")
        else:
            logger.info("Using default configuration")

    def save(self) -> None:
        """Save current configuration to file."""
        CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
        # Convert dataclass to dict for JSON serialization
        config_dict = {}
        for attr_name in dir(self):
            if not attr_name.startswith("_") and attr_name not in {
                "WIN_SIZE",
                "WIN_POS",
                "MAX_TOKENS_RANGE",
                "TEMPERATURE_RANGE",
                "TOP_P_RANGE",
                "TOP_K_RANGE",
            }:
                try:
                    attr_value = getattr(self, attr_name)
                    if not callable(attr_value):
                        config_dict[attr_name] = attr_value
                except AttributeError:
                    continue
        CONFIG_FILE.write_text(
            json.dumps(config_dict, indent=4, ensure_ascii=False),
            encoding="utf-8",
        )


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


class LLMWorker(QThread):
    """LLM server communication worker thread.

    Handles HTTP streaming requests in background thread to avoid blocking UI main thread.
    Uses incremental UTF-8 decoder to correctly handle multi-byte characters across lines,
    preventing garbled text from truncated multi-byte characters.

    Signals:
        response_received: Emitted when response content is received, carries response text
        error_occurred: Emitted when error occurs, carries error message
        is_finished: Emitted when request completes
    """

    response_received = Signal(str)
    error_occurred = Signal(str)
    is_finished = Signal()

    def __init__(
        self,
        prompt: str,
        server_url: str,
        max_tokens: int,
        temperature: float,
        top_p: float,
        top_k: int,
    ) -> None:
        """Initialize LLM worker thread.

        Args:
            prompt: User input prompt text
            server_url: LLM server address
            max_tokens: Maximum number of tokens to generate
            temperature: Temperature parameter controlling randomness (0.0-2.0)
            top_p: Nucleus sampling parameter (0.0-1.0)
            top_k: Number of candidate tokens to retain
        """
        super().__init__()
        self.prompt = prompt
        self.server_url = server_url
        self.max_tokens = max_tokens
        self.temperature = temperature
        self.top_p = top_p
        self.top_k = top_k
        self._is_running = True

    def run(self) -> None:
        """Execute streaming HTTP request and process response.

        Receives streaming response using Server-Sent Events (SSE) format.
        Uses incremental UTF-8 decoder to avoid garbled text from truncated multi-byte characters,
        ensuring full-chain character encoding consistency (request/response both use UTF-8).
        """
        try:
            headers = {"Content-Type": "application/json; charset=utf-8"}
            data = {
                "prompt": self.prompt,
                "max_tokens": self.max_tokens,
                "temperature": self.temperature,
                "top_p": self.top_p,
                "top_k": self.top_k,
                "stream": True,
            }

            request = Request(
                f"{self.server_url}/completion",
                data=json.dumps(data, ensure_ascii=False).encode("utf-8"),
                headers=headers,
            )

            with urlopen(request) as response:
                if response.status != 200:
                    error_text = response.read().decode("utf-8")
                    self.error_occurred.emit(
                        f"Error: {response.status} - {error_text}",
                    )
                    return

                decoder = getincrementaldecoder("utf-8")(errors="replace")
                buffer = ""

                for line in response:
                    if not self._is_running:
                        break

                    if line:
                        try:
                            decoded_line = decoder.decode(line, False).strip()
                            if not decoded_line:
                                continue

                            if decoded_line.startswith("data: "):
                                json_str = decoded_line[6:]
                                try:
                                    json_data = json.loads(json_str)
                                    content = json_data.get("content", "")
                                    if content:
                                        buffer += content
                                        self.response_received.emit(buffer)
                                except json.JSONDecodeError as e:
                                    logger.debug(
                                        f"JSON parsing failed: {json_str}, error: {e}",
                                    )
                                    continue
                        except Exception as e:
                            logger.debug(f"Line processing failed: {e}")
                            continue

                decoder.decode(b"", True)

        except URLError as e:
            logger.exception(f"Connection error: {e.reason}")
            self.error_occurred.emit(f"Connection error: {e.reason}")
        except Exception as e:
            logger.exception(f"Request error: {e!s}")
            self.error_occurred.emit(f"Request error: {e!s}")
        finally:
            self.is_finished.emit()

    def stop(self) -> None:
        """Stop worker thread execution gracefully.

        Sets the internal flag to signal the worker thread to terminate its
        ongoing operations. The thread will complete its current iteration
        and exit cleanly.
        """
        self._is_running = False


class ConnectionTestWorker(QThread):
    """Server connection test worker thread.

    Tests whether the LLM server's health check endpoint is accessible in background thread.

    Signals:
        result_ready: Emitted when test completes, passes (success_flag, message_text)
    """

    result_ready = Signal(bool, str)

    def __init__(self, server_url: str) -> None:
        """Initialize connection test worker thread.

        Args:
            server_url: LLM server address
        """
        super().__init__()
        self.server_url = server_url

    def run(self) -> None:
        """Execute connection test by accessing server health check endpoint.

        Attempts to connect to the server's /health endpoint with a timeout.
        Emits result_ready signal with success status and descriptive message.
        """
        try:
            request = Request(f"{self.server_url}/health")
            with urlopen(request, timeout=CONNECTION_TIMEOUT) as response:
                if response.status == 200:
                    self.result_ready.emit(True, "Connection successful!")
                else:
                    self.result_ready.emit(
                        False,
                        f"Connection failed: {response.status}",
                    )
        except URLError as e:
            logger.exception(f"Connection error: {e.reason}")
            self.result_ready.emit(False, f"Connection error: {e.reason}")
        except Exception as e:
            logger.exception(f"Request error: {e!s}")
            self.result_ready.emit(False, f"Request error: {e!s}")


class LLMChatApp(QMainWindow):
    """LLM Chat client main window with modern UI styling.

    Provides graphical interface for interacting with LLM server, supports:
    - Real-time streaming response display (uses incremental updates to avoid repeated rendering)
    - Connection testing
    - Model parameter adjustment (temperature, top-p, top-k, etc.)
    - Conversation history tracking
    - Multi-language support
    - Modern compact UI design
    """

    def __init__(self) -> None:
        """Initialize LLM Chat main window with modern styling."""
        super().__init__()
        self.setWindowTitle(_translation_manager.tr("window_title"))
        self.setGeometry(*conf.WIN_POS, *conf.WIN_SIZE)

        # Apply modern styling
        self._apply_styles()

        self.init_ui()

        # Add language selector
        self._add_language_selector()

        self.worker_thread: LLMWorker | None = None
        self.test_thread: ConnectionTestWorker | None = None
        self.current_ai_start_pos = -1

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

    def _add_language_selector(self) -> None:
        """Add language selection combobox with label to the main window."""
        # Create horizontal layout for language selector with minimal height
        language_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )

        # Create language label and store reference for updates
        self.language_label = QLabel(_translation_manager.tr("language"))
        self.language_label.setStyleSheet(
            f"font-weight: bold; font-size: {_UI_CONSTANTS['FONT_SIZE_SMALL']}px;",
        )

        # Create language selector
        self.language_combo = QComboBox()
        self.language_combo.setMinimumWidth(_UI_CONSTANTS["LANGUAGE_COMBO_WIDTH"])
        self.language_combo.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.language_combo.setStyleSheet("padding: 2px 4px;")
        for lang_code, lang_name in _translation_manager.get_available_languages():
            self.language_combo.addItem(lang_name, lang_code)

        # Set default based on configuration
        current_lang_index = 0 if conf.language == "zh" else 1
        self.language_combo.setCurrentIndex(current_lang_index)
        self.language_combo.currentIndexChanged.connect(self._change_language)

        # Add widgets to layout
        language_layout.addWidget(self.language_label)
        language_layout.addWidget(self.language_combo)
        language_layout.addStretch()  # Push to left side

        # Add to main layout
        main_layout = self.centralWidget().layout()
        if main_layout:
            # Insert language selector at the top
            main_layout.insertLayout(0, language_layout)

    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)
            conf.language = "zh"
        else:
            _translation_manager.set_language(Language.ENGLISH)
            conf.language = "en"

        # Update all UI elements
        self._update_ui_text()

    def _update_ui_text(self) -> None:
        """Update all UI text elements to current language."""
        # Update window title
        self.setWindowTitle(_translation_manager.tr("window_title"))

        # Update group box titles
        self.server_group.setTitle(_translation_manager.tr("server_settings"))
        self.params_group.setTitle(_translation_manager.tr("model_parameters"))
        self.chat_group.setTitle(_translation_manager.tr("chat_history"))

        # Update language selector label
        if hasattr(self, "language_label"):
            self.language_label.setText(_translation_manager.tr("language"))

        # Update labels
        self.server_label.setText(_translation_manager.tr("server_address"))
        self.max_tokens_label.setText(_translation_manager.tr("max_tokens"))
        self.temperature_label.setText(_translation_manager.tr("temperature"))
        self.top_p_label.setText(_translation_manager.tr("top_p"))
        self.top_k_label.setText(_translation_manager.tr("top_k"))
        self.input_label.setText(_translation_manager.tr("user_input"))

        # Update button texts
        self.test_connection_btn.setText(_translation_manager.tr("test_connection"))
        self.send_btn.setText(_translation_manager.tr("send"))
        self.stop_btn.setText(_translation_manager.tr("stop"))
        self.clear_btn.setText(_translation_manager.tr("clear_history"))

        # Update status bar
        self.statusBar().showMessage(_translation_manager.tr("ready"))

    def init_ui(self) -> None:
        """Initialize user interface components and layout with modern design.

        Sets up the main window layout with server settings, model parameters,
        chat display area, and input controls. Establishes the complete GUI
        structure for user interaction with compact styling.
        """
        main_widget = QWidget()
        main_widget.setObjectName("centralWidget")

        # Create compact main layout
        main_layout = LayoutFactory.create_vertical_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
        )
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

        # Create interface components with compact design
        self.server_group = self._create_server_group()
        self.params_group = self._create_params_group()
        self.chat_group = self._create_chat_display_group()
        input_layout = self._create_input_layout()

        # Add components to main layout
        main_layout.addWidget(self.server_group)
        main_layout.addWidget(self.params_group)
        main_layout.addWidget(self.chat_group, 1)  # Expand chat area
        main_layout.addLayout(input_layout)

        self.statusBar().showMessage(_translation_manager.tr("ready"))

    def _create_server_group(self) -> QGroupBox:
        """Create server settings group with compact layout.

        Returns
        -------
            Group box containing server address input and test connection button
        """
        server_group = QGroupBox(_translation_manager.tr("server_settings"))
        server_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
        )

        # Create server address row
        server_row = QWidget()
        server_row_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        server_row.setLayout(server_row_layout)

        self.server_label = QLabel(_translation_manager.tr("server_address"))
        self.server_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["LABEL_TEXT_COLOR"]};
            padding: {_UI_CONSTANTS["LABEL_PADDING_VERTICAL"]}px {_UI_CONSTANTS["LABEL_PADDING_HORIZONTAL"]}px;
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
            """,
        )

        self.server_url_input = QLineEdit(conf.SERVER_URL)
        self.server_url_input.setMinimumWidth(_UI_CONSTANTS["INPUT_WIDTH_MIN"])
        self.server_url_input.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.server_url_input.setStyleSheet("padding: 2px 8px;")
        self.server_url_input.setPlaceholderText("http://localhost:8080")
        self.server_url_input.textChanged.connect(self.on_config_changed)

        self.test_connection_btn = QPushButton(
            _translation_manager.tr("test_connection"),
        )
        self.test_connection_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_MEDIUM"])
        self.test_connection_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.test_connection_btn.setStyleSheet("padding: 2px 8px;")
        self.test_connection_btn.clicked.connect(self.test_connection)

        server_row_layout.addWidget(self.server_label)
        server_row_layout.addWidget(self.server_url_input, 1)
        server_row_layout.addWidget(self.test_connection_btn)

        server_layout.addWidget(server_row)
        server_group.setLayout(server_layout)

        return server_group

    def _create_params_group(self) -> QGroupBox:
        """Create model parameter settings group with compact layout.

        Returns
        -------
            Group box containing all model parameter adjustment controls
        """
        params_group = QGroupBox(_translation_manager.tr("model_parameters"))
        params_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
        )

        # Create parameter rows
        param_rows = []

        # Max Tokens row
        max_tokens_row = self._create_param_row(
            "max_tokens",
            conf.MAX_TOKENS_RANGE,
            conf.MAX_TOKENS,
            self._on_max_tokens_changed,
        )
        self.max_tokens_label = max_tokens_row[0]
        self.max_tokens_spin = max_tokens_row[1]
        param_rows.append(max_tokens_row)

        # Temperature row
        temp_row = self._create_param_row(
            "temperature",
            conf.TEMPERATURE_RANGE,
            conf.TEMPERATURE,
            self._on_temperature_changed,
            is_double=True,
        )
        self.temperature_label = temp_row[0]
        self.temperature_spin = temp_row[1]
        param_rows.append(temp_row)

        # Top-P row
        top_p_row = self._create_param_row(
            "top_p",
            conf.TOP_P_RANGE,
            conf.TOP_P,
            self._on_top_p_changed,
            is_double=True,
            step=0.05,
        )
        self.top_p_label = top_p_row[0]
        self.top_p_spin = top_p_row[1]
        param_rows.append(top_p_row)

        # Top-K row
        top_k_row = self._create_param_row(
            "top_k",
            conf.TOP_K_RANGE,
            conf.TOP_K,
            self._on_top_k_changed,
        )
        self.top_k_label = top_k_row[0]
        self.top_k_spin = top_k_row[1]
        param_rows.append(top_k_row)

        # Arrange parameter rows in grid
        grid_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )

        # Add parameter rows to grid (2 rows x 4 columns)
        for label, spinbox in param_rows[:2]:
            grid_layout.addWidget(label)
            grid_layout.addWidget(spinbox)

        second_row = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        for label, spinbox in param_rows[2:]:
            second_row.addWidget(label)
            second_row.addWidget(spinbox)

        params_layout.addLayout(grid_layout)
        params_layout.addLayout(second_row)
        params_group.setLayout(params_layout)

        return params_group

    def _create_param_row(
        self,
        param_name: str,
        range_vals: list,
        default_val,
        callback,
        is_double: bool = False,
        step: float = 0.1,
    ):
        """Create a parameter row with label and spinbox."""
        # Create label
        label = QLabel(_translation_manager.tr(param_name))
        label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["LABEL_TEXT_COLOR"]};
            padding: {_UI_CONSTANTS["LABEL_PADDING_VERTICAL"]}px {_UI_CONSTANTS["LABEL_PADDING_HORIZONTAL"]}px;
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
            """,
        )

        # Create spinbox
        if is_double:
            spinbox = QDoubleSpinBox()
            spinbox.setRange(range_vals[0], range_vals[1])
            spinbox.setSingleStep(step)
            spinbox.setValue(default_val)
        else:
            spinbox = QSpinBox()
            spinbox.setRange(range_vals[0], range_vals[1])
            spinbox.setValue(default_val)

        spinbox.setMinimumWidth(100)
        spinbox.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        spinbox.setStyleSheet("padding: 2px 8px;")
        spinbox.valueChanged.connect(callback)

        return label, spinbox

    def _on_max_tokens_changed(self, value: int) -> None:
        """Handle max tokens value change."""
        conf.MAX_TOKENS = value

    def _on_temperature_changed(self, value: float) -> None:
        """Handle temperature value change."""
        conf.TEMPERATURE = value

    def _on_top_p_changed(self, value: float) -> None:
        """Handle top-p value change."""
        conf.TOP_P = value

    def _on_top_k_changed(self, value: int) -> None:
        """Handle top-k value change."""
        conf.TOP_K = value

    def _create_chat_display_group(self) -> QGroupBox:
        """Create chat display group with compact layout.

        Returns
        -------
            Group box containing chat display area and control buttons
        """
        chat_group = QGroupBox(_translation_manager.tr("chat_history"))
        chat_layout = LayoutFactory.create_vertical_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
        )

        # Create chat display area
        self.chat_display = QTextEdit()
        self.chat_display.setObjectName("chatDisplay")
        self.chat_display.setReadOnly(True)
        self.chat_display.setMinimumHeight(300)
        self.chat_display.setStyleSheet("padding: 12px 16px;")

        # Create control buttons row
        control_row = QWidget()
        control_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        control_row.setLayout(control_layout)

        self.clear_btn = QPushButton(_translation_manager.tr("clear_history"))
        self.clear_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_MEDIUM"])
        self.clear_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.clear_btn.setStyleSheet("padding: 2px 8px;")
        self.clear_btn.clicked.connect(self._clear_chat_history)

        control_layout.addStretch()
        control_layout.addWidget(self.clear_btn)

        # Assemble chat group
        chat_layout.addWidget(self.chat_display, 1)
        chat_layout.addWidget(control_row)
        chat_group.setLayout(chat_layout)

        return chat_group

    def _clear_chat_history(self) -> None:
        """Clear chat history display."""
        self.chat_display.clear()
        self.statusBar().showMessage(_translation_manager.tr("ready"))

    def _create_input_layout(self) -> QHBoxLayout:
        """Create user input area layout with compact design.

        Returns
        -------
            Layout containing input box and send/stop buttons
        """
        input_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(_UI_CONSTANTS["SMALL_MARGIN"],) * 4,
        )

        # Create input row
        input_row = QWidget()
        input_row_layout = LayoutFactory.create_compact_layout(
            spacing=_UI_CONSTANTS["COMPACT_SPACING"],
            margins=(0, _UI_CONSTANTS["MIN_MARGIN"], 0, _UI_CONSTANTS["MIN_MARGIN"]),
        )
        input_row.setLayout(input_row_layout)

        # Add input label
        self.input_label = QLabel(_translation_manager.tr("user_input"))
        self.input_label.setStyleSheet(
            f"""
            font-size: {_UI_CONSTANTS["FONT_SIZE_MEDIUM"]}px;
            font-weight: 500;
            color: {_UI_CONSTANTS["LABEL_TEXT_COLOR"]};
            padding: {_UI_CONSTANTS["LABEL_PADDING_VERTICAL"]}px {_UI_CONSTANTS["LABEL_PADDING_HORIZONTAL"]}px;
            font-family: {_UI_CONSTANTS["FONT_FAMILY"]};
            min-height: {_UI_CONSTANTS["INPUT_HEIGHT"]}px;
            """,
        )

        self.user_input = QLineEdit()
        self.user_input.setMinimumWidth(_UI_CONSTANTS["INPUT_WIDTH_MIN"])
        self.user_input.setMinimumHeight(_UI_CONSTANTS["INPUT_HEIGHT"])
        self.user_input.setStyleSheet("padding: 2px 8px;")
        self.user_input.setPlaceholderText("Enter your prompt...")
        self.user_input.returnPressed.connect(self.send_prompt)

        self.send_btn = QPushButton(_translation_manager.tr("send"))
        self.send_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_SMALL"])
        self.send_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.send_btn.setStyleSheet("padding: 2px 8px;")
        self.send_btn.clicked.connect(self.send_prompt)

        self.stop_btn = QPushButton(_translation_manager.tr("stop"))
        self.stop_btn.setMinimumWidth(_UI_CONSTANTS["BUTTON_WIDTH_SMALL"])
        self.stop_btn.setMinimumHeight(_UI_CONSTANTS["BUTTON_HEIGHT"])
        self.stop_btn.setStyleSheet("padding: 2px 8px;")
        self.stop_btn.clicked.connect(self.stop_generation)
        self.stop_btn.setEnabled(False)

        input_row_layout.addWidget(self.input_label)
        input_row_layout.addWidget(self.user_input, 1)
        input_row_layout.addWidget(self.send_btn)
        input_row_layout.addWidget(self.stop_btn)

        input_layout.addWidget(input_row)

        return input_layout

    def on_config_changed(self) -> None:
        """Handle configuration changes with validation."""
        conf.SERVER_URL = self.server_url_input.text().strip()
        # Other parameters are handled by individual callbacks

    def test_connection(self) -> None:
        """Test connection with LLM server.

        Sends health check request in background thread to avoid blocking UI.
        """
        server_url = self.server_url_input.text().strip()
        if not server_url:
            self.statusBar().showMessage(_translation_manager.tr("please_enter_server"))
            return

        if self.test_thread and self.test_thread.isRunning():
            self.statusBar().showMessage(
                _translation_manager.tr("waiting_for_response"),
            )
            return

        self.test_thread = ConnectionTestWorker(server_url)
        self.test_thread.result_ready.connect(self.on_connection_test_result)
        self.test_thread.finished.connect(self.on_test_thread_finished)

        self.statusBar().showMessage(_translation_manager.tr("waiting_for_response"))
        self.test_connection_btn.setEnabled(False)

        self.test_thread.start()

    def on_test_thread_finished(self) -> None:
        """Handle test thread completion event and clean up resources."""
        if self.test_thread:
            self.test_thread.quit()
            self.test_thread.wait()
            self.test_thread = None

    def on_connection_test_result(self, success: bool, message: str) -> None:
        """Handle connection test result with translated messages.

        Args:
            success: Whether connection succeeded
            message: Result message text
        """
        if success:
            self.statusBar().showMessage(
                _translation_manager.tr("connection_successful"),
            )
        else:
            self.statusBar().showMessage(_translation_manager.tr("connection_failed"))
        self.test_connection_btn.setEnabled(True)

    def send_prompt(self) -> None:
        """Send user input prompt to LLM server.

        Creates worker thread to handle streaming response and updates UI to display conversation.
        Resets AI reply start position to avoid repeated rendering issues.
        """
        if self.worker_thread and self.worker_thread.isRunning():
            self.statusBar().showMessage(
                _translation_manager.tr("waiting_for_response"),
            )
            return

        prompt = self.user_input.text().strip()
        if not prompt:
            self.statusBar().showMessage(_translation_manager.tr("please_enter_prompt"))
            return

        self.current_ai_start_pos = -1

        server_url = self.server_url_input.text().strip()
        max_tokens = self.max_tokens_spin.value()
        temperature = self.temperature_spin.value()
        top_p = self.top_p_spin.value()
        top_k = self.top_k_spin.value()

        self._display_user_input(prompt, server_url)
        self.user_input.clear()

        logger.info(f"Sending prompt: {prompt}")
        logger.info(
            f"Parameters: max_tokens={max_tokens}, temperature={temperature}, top_p={top_p}, top_k={top_k}",
        )

        self.worker_thread = LLMWorker(
            prompt=prompt,
            server_url=server_url,
            max_tokens=max_tokens,
            temperature=temperature,
            top_p=top_p,
            top_k=top_k,
        )

        self.worker_thread.response_received.connect(self.update_response)
        self.worker_thread.error_occurred.connect(self.handle_error)
        self.worker_thread.is_finished.connect(self.on_finished)

        self.send_btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        self.statusBar().showMessage(_translation_manager.tr("generating_response"))

        self.worker_thread.start()

    def _display_user_input(self, prompt: str, server_url: str) -> None:
        """Display user input and target in chat area with colored text.

        Args:
            prompt: User input prompt text
            server_url: Target server address
        """
        cursor = self.chat_display.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.chat_display.setTextCursor(cursor)

        # User message in blue
        self.chat_display.setTextColor(Qt.blue)
        self.chat_display.insertPlainText(f"You: {prompt}\n")

        # Server info in gray
        self.chat_display.setTextColor(Qt.darkGray)
        self.chat_display.insertPlainText(f"[Sending to {server_url}]\n")

        # AI response header in black
        self.chat_display.setTextColor(Qt.black)
        self.chat_display.insertPlainText("AI: ")

    def stop_generation(self) -> None:
        """Stop current ongoing response generation."""
        if self.worker_thread and self.worker_thread.isRunning():
            self.worker_thread.stop()
            self.statusBar().showMessage(_translation_manager.tr("generation_stopped"))

    def update_response(self, text: str) -> None:
        """Update AI response content in chat display area.

        Args:
            text: Complete AI generated response text
        """
        cursor = self.chat_display.textCursor()

        if self.current_ai_start_pos == -1:
            cursor.movePosition(QTextCursor.End)
            self.current_ai_start_pos = cursor.position()
            self.chat_display.insertPlainText(f" {text}")
        else:
            cursor.setPosition(self.current_ai_start_pos)
            cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
            cursor.removeSelectedText()
            self.chat_display.setTextCursor(cursor)
            self.chat_display.insertPlainText(f" {text}")

        self.chat_display.ensureCursorVisible()

    def append_to_chat(self, text: str, *, is_user: bool = False) -> None:
        """Append text to chat area.

        Args:
            text: Text content to append
            is_user: Whether this is a user message (for color setting)
        """
        cursor = self.chat_display.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.chat_display.setTextCursor(cursor)

        if is_user:
            self.chat_display.setTextColor(Qt.blue)
        else:
            self.chat_display.setTextColor(Qt.black)

        self.chat_display.insertPlainText(text + "\n")
        self.chat_display.setTextColor(Qt.black)

    def handle_error(self, error_msg: str) -> None:
        """Handle error messages.

        Args:
            error_msg: Error message text
        """
        self.append_to_chat(f"Error: {error_msg}")
        self.statusBar().showMessage(error_msg)

    def on_finished(self) -> None:
        """Handle response generation completion event, restore UI state and clean up resources."""
        self.send_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        self.statusBar().showMessage(_translation_manager.tr("generation_complete"))
        self.append_to_chat("")

        if self.worker_thread:
            self.worker_thread.quit()
            self.worker_thread.wait()
            self.worker_thread = None

    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)


# Multi-language support
class Language(Enum):
    """Supported languages."""

    CHINESE = "zh"
    ENGLISH = "en"


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

    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": "LLM聊天客户端 v1.0",
                # Language selector
                "language": "语言",
                # Group box titles
                "server_settings": "服务器设置",
                "model_parameters": "模型参数",
                "chat_history": "对话历史",
                # Labels
                "server_address": "服务器地址",
                "max_tokens": "最大令牌数",
                "temperature": "温度",
                "top_p": "Top-P",
                "top_k": "Top-K",
                "user_input": "用户输入",
                # Buttons
                "test_connection": "测试连接",
                "send": "发送",
                "stop": "停止",
                "clear_history": "清空历史",
                # Messages
                "connection_successful": "连接成功!",
                "connection_failed": "连接失败",
                "please_enter_server": "请输入服务器地址",
                "please_enter_prompt": "请输入提示词",
                "generating_response": "正在生成响应...",
                "generation_complete": "生成完成",
                "generation_stopped": "生成已停止",
                "ready": "就绪",
                "waiting_for_response": "等待响应...",
            },
            Language.ENGLISH.value: {
                # Window title
                "window_title": "LLM Chat Client v1.0",
                # Language selector
                "language": "Language",
                # Group box titles
                "server_settings": "Server Settings",
                "model_parameters": "Model Parameters",
                "chat_history": "Chat History",
                # Labels
                "server_address": "Server Address",
                "max_tokens": "Max Tokens",
                "temperature": "Temperature",
                "top_p": "Top-P",
                "top_k": "Top-K",
                "user_input": "User Input",
                # Buttons
                "test_connection": "Test Connection",
                "send": "Send",
                "stop": "Stop",
                "clear_history": "Clear History",
                # Messages
                "connection_successful": "Connection successful!",
                "connection_failed": "Connection failed",
                "please_enter_server": "Please enter server address",
                "please_enter_prompt": "Please enter prompt",
                "generating_response": "Generating response...",
                "generation_complete": "Generation complete",
                "generation_stopped": "Generation stopped",
                "ready": "Ready",
                "waiting_for_response": "Waiting for response...",
            },
        }

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

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

    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()


def main() -> None:
    """Application entry point."""
    app = QApplication(sys.argv)
    app.setStyle("Fusion")

    window = LLMChatApp()
    window.show()

    sys.exit(app.exec_())
