import os
import shlex
import shutil
import signal
import subprocess
import sys

import portprotonqt.themes.standart.styles as default_styles
import psutil

from portprotonqt.dialogs import AddGameDialog, FileExplorer
from portprotonqt.game_card import GameCard
from portprotonqt.animations import DetailPageAnimations
from portprotonqt.custom_widgets import FlowLayout, ClickableLabel, AutoSizeButton, NavLabel
from portprotonqt.portproton_api import PortProtonAPI
from portprotonqt.input_manager import InputManager
from portprotonqt.context_menu_manager import ContextMenuManager, CustomLineEdit
from portprotonqt.system_overlay import SystemOverlay

from portprotonqt.image_utils import load_pixmap_async, round_corners, ImageCarousel
from portprotonqt.steam_api import get_steam_game_info_async, get_full_steam_game_info_async, get_steam_installed_games
from portprotonqt.egs_api import load_egs_games_async, get_egs_executable
from portprotonqt.theme_manager import ThemeManager, load_theme_screenshots, load_logo
from portprotonqt.time_utils import save_last_launch, get_last_launch, parse_playtime_file, format_playtime, get_last_launch_timestamp, format_last_launch
from portprotonqt.config_utils import (
    get_portproton_location, read_theme_from_config, save_theme_to_config, parse_desktop_entry,
    load_theme_metainfo, read_time_config, read_card_size, save_card_size, read_sort_method,
    read_display_filter, read_favorites, save_favorites, save_time_config, save_sort_method,
    save_display_filter, save_proxy_config, read_proxy_config, read_fullscreen_config,
    save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config,
    clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config
)
from portprotonqt.localization import _, get_egs_language, read_metadata_translations
from portprotonqt.logger import get_logger
from portprotonqt.howlongtobeat_api import HowLongToBeat
from portprotonqt.downloader import Downloader

from PySide6.QtWidgets import (QLineEdit, QMainWindow, QStatusBar, QWidget, QVBoxLayout, QLabel, QHBoxLayout, QStackedWidget, QComboBox, QScrollArea, QSlider,
                               QDialog, QFormLayout, QFrame, QGraphicsDropShadowEffect, QMessageBox, QApplication, QPushButton, QProgressBar, QCheckBox, QSizePolicy)
from PySide6.QtCore import Qt, QAbstractAnimation, QUrl, Signal, QTimer, Slot
from PySide6.QtGui import QIcon, QPixmap, QColor, QDesktopServices
from typing import cast
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime

logger = get_logger(__name__)

class MainWindow(QMainWindow):
    """Main window of PortProtonQt."""
    games_loaded = Signal(list)
    update_progress = Signal(int)  # Signal to update progress bar
    update_status_message = Signal(str, int)  # Signal to update status message

    def __init__(self):
        super().__init__()
        # Создаём менеджер тем и читаем, какая тема выбрана
        self.theme_manager = ThemeManager()
        selected_theme = read_theme_from_config()
        self.current_theme_name = selected_theme
        try:
            self.theme = self.theme_manager.apply_theme(selected_theme)
        except FileNotFoundError:
            logger.warning(f"Тема '{selected_theme}' не найдена, применяется стандартная тема 'standart'")
            self.theme = self.theme_manager.apply_theme("standart")
            self.current_theme_name = "standart"
            save_theme_to_config("standart")
        if not self.theme:
            self.theme = default_styles
        self.card_width = read_card_size()
        self.setWindowTitle("PortProtonQt")
        self.setMinimumSize(800, 600)

        self.games = []
        self.filtered_games = self.games
        self.game_processes = []
        self.target_exe = None
        self.current_running_button = None
        self.portproton_location = get_portproton_location()

        self.context_menu_manager = ContextMenuManager(
            self,
            self.portproton_location,
            self.theme,
            self.loadGames,
            self.updateGameGrid
        )

        QApplication.setStyle("Fusion")
        self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE)
        self.setAcceptDrops(True)
        self.current_exec_line = None
        self.currentDetailPage = None
        self.current_play_button = None
        self.current_focused_card = None
        self.pending_games = []
        self.game_card_cache = {}
        self.pending_images = {}
        self.total_games = 0
        self.games_load_timer = QTimer(self)
        self.games_load_timer.setSingleShot(True)
        self.games_load_timer.timeout.connect(self.finalize_game_loading)
        self.games_loaded.connect(self.on_games_loaded)
        self.current_add_game_dialog = None
        self.current_hovered_card = None

        # Добавляем таймер для дебаунсинга сохранения настроек
        self.settingsDebounceTimer = QTimer(self)
        self.settingsDebounceTimer.setSingleShot(True)
        self.settingsDebounceTimer.setInterval(300)  # 300 мс задержка
        self.settingsDebounceTimer.timeout.connect(self.applySettingsDelayed)

        read_time_config()
        # Set LEGENDARY_CONFIG_PATH to ~/.cache/PortProtonQt/legendary_cache
        self.legendary_config_path = os.path.join(
            os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
            "PortProtonQt", "legendary_cache"
        )
        os.makedirs(self.legendary_config_path, exist_ok=True)
        os.environ["LEGENDARY_CONFIG_PATH"] = self.legendary_config_path

        self.legendary_path = os.path.join(self.legendary_config_path, "legendary")
        self.downloader = Downloader(max_workers=4)
        self.portproton_api = PortProtonAPI(self.downloader)

        # Статус-бар
        self.setStatusBar(QStatusBar(self))
        self.statusBar().setStyleSheet(self.theme.STATUS_BAR_STYLE)
        self.progress_bar = QProgressBar()
        self.progress_bar.setStyleSheet(self.theme.PROGRESS_BAR_STYLE)
        self.progress_bar.setMaximumWidth(200)
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setVisible(False)
        self.statusBar().addPermanentWidget(self.progress_bar)
        self.update_progress.connect(self.progress_bar.setValue)
        self.update_status_message.connect(self.statusBar().showMessage)

        # Центральный виджет и основной layout
        centralWidget = QWidget()
        self.setCentralWidget(centralWidget)
        mainLayout = QVBoxLayout(centralWidget)
        mainLayout.setSpacing(0)
        mainLayout.setContentsMargins(0, 0, 0, 0)

        # 1. ШАПКА (HEADER)
        self.header = QWidget()
        self.header.setFixedHeight(80)
        self.header.setStyleSheet(self.theme.MAIN_WINDOW_HEADER_STYLE)
        headerLayout = QVBoxLayout(self.header)
        headerLayout.setContentsMargins(0, 0, 0, 0)

        # Текст "PortProton" слева
        self.titleLabel = QLabel()
        pixmap = load_logo()
        if pixmap is None:
            width, height = self.theme.pixmapsScaledSize
            pixmap = QPixmap(width, height)
            pixmap.fill(QColor(0, 0, 0, 0))
        width, height = self.theme.pixmapsScaledSize
        scaled_pixmap = pixmap.scaled(width, height,
                                    Qt.AspectRatioMode.KeepAspectRatio,
                                    Qt.TransformationMode.SmoothTransformation)
        self.titleLabel.setPixmap(scaled_pixmap)
        self.titleLabel.setFixedSize(scaled_pixmap.size())
        self.titleLabel.setStyleSheet(self.theme.TITLE_LABEL_STYLE)
        headerLayout.addStretch()

        # 2. НАВИГАЦИЯ (КНОПКИ ВКЛАДОК)
        self.navWidget = QWidget()
        self.navWidget.setStyleSheet(self.theme.NAV_WIDGET_STYLE)
        navLayout = QHBoxLayout(self.navWidget)
        navLayout.setContentsMargins(10, 0, 10, 0)
        navLayout.setSpacing(0)

        navLayout.addWidget(self.titleLabel)

        self.tabButtons = {}
        tabs = [
            _("Library"),
            _("Auto Install"),
            _("Emulators"),
            _("Wine Settings"),
            _("PortProton Settings"),
            _("Themes")
        ]
        for i, tabName in enumerate(tabs):
            btn = NavLabel(tabName)
            btn.setCheckable(True)
            btn.clicked.connect(lambda index=i: self.switchTab(index))
            btn.setStyleSheet(self.theme.NAV_BUTTON_STYLE)
            navLayout.addWidget(btn)
            self.tabButtons[i] = btn

        self.tabButtons[0].setChecked(True)
        mainLayout.addWidget(self.navWidget)

        # 3. QStackedWidget (ВКЛАДКИ)
        self.stackedWidget = QStackedWidget()
        mainLayout.addWidget(self.stackedWidget)

        # Создаём все вкладки
        self.createInstalledTab()    # вкладка 0
        self.createAutoInstallTab()  # вкладка 1
        self.createEmulatorsTab()    # вкладка 2
        self.createWineTab()         # вкладка 3
        self.createPortProtonTab()   # вкладка 4
        self.createThemeTab()        # вкладка 5

        self.restore_state()

        self.input_manager = InputManager(self)
        self.detail_animations = DetailPageAnimations(self, self.theme)
        QTimer.singleShot(0, self.loadGames)

        if read_fullscreen_config():
            self.showFullScreen()
        else:
            width, height = read_window_geometry()
            if width > 0 and height > 0:
                self.resize(width, height)
            else:
                self.showNormal()
    @Slot(list)
    def on_games_loaded(self, games: list[tuple]):
        self.games = games
        favorites = read_favorites()
        sort_method = read_sort_method()

        # Sort by: favorites first, then descending playtime, then descending last launch
        if sort_method == "playtime":
            self.games.sort(key=lambda g: (0 if g[0] in favorites else 1, -g[11], -g[10]))

        # Sort by: favorites first, then alphabetically by game name
        elif sort_method == "alphabetical":
            self.games.sort(key=lambda g: (0 if g[0] in favorites else 1, g[0].lower()))

        # Sort by: favorites first, then leave the rest in their original order
        elif sort_method == "favorites":
            self.games.sort(key=lambda g: (0 if g[0] in favorites else 1))

        # Sort by: favorites first, then descending last launch, then descending playtime
        elif sort_method == "last_launch":
            self.games.sort(key=lambda g: (0 if g[0] in favorites else 1, -g[10], -g[11]))

        # Fallback: same as last_launch
        else:
            self.games.sort(key=lambda g: (0 if g[0] in favorites else 1, -g[10], -g[11]))

        self.updateGameGrid()
        self.progress_bar.setVisible(False)

    def open_portproton_forum_topic(self, topic_name: str):
        """Open the PortProton forum topic or search page for this game."""
        result = self.portproton_api.get_forum_topic_slug(topic_name)
        base_url = "https://linux-gaming.ru/"
        if result.startswith("search?q="):
            url = QUrl(f"{base_url}{result}")
        else:
            url = QUrl(f"{base_url}t/{result}")
        QDesktopServices.openUrl(url)

    def _on_card_focused(self, game_name: str, is_focused: bool):
        """Обработчик сигнала focusChanged от GameCard."""
        card_key = None
        for key, card in self.game_card_cache.items():
            if card.name == game_name:
                card_key = key
                break

        if not card_key:
            return

        card = self.game_card_cache[card_key]

        if is_focused:
            # Если карточка получила фокус
            if self.current_hovered_card and self.current_hovered_card != card:
                # Сбрасываем текущую hovered карточку
                self.current_hovered_card._hovered = False
                self.current_hovered_card.leaveEvent(None)
                self.current_hovered_card = None
            if self.current_focused_card and self.current_focused_card != card:
                # Сбрасываем текущую focused карточку
                self.current_focused_card._focused = False
                self.current_focused_card.clearFocus()
            self.current_focused_card = card
        else:
            # Если карточка потеряла фокус
            if self.current_focused_card == card:
                self.current_focused_card = None

    def _on_card_hovered(self, game_name: str, is_hovered: bool):
        """Обработчик сигнала hoverChanged от GameCard."""
        card_key = None
        for key, card in self.game_card_cache.items():
            if card.name == game_name:
                card_key = key
                break

        if not card_key:
            return

        card = self.game_card_cache[card_key]

        if is_hovered:
            # Если мышь наведена на карточку
            if self.current_focused_card and self.current_focused_card != card:
                # Сбрасываем текущую focused карточку
                self.current_focused_card._focused = False
                self.current_focused_card.clearFocus()
            if self.current_hovered_card and self.current_hovered_card != card:
                # Сбрасываем предыдущую hovered карточку
                self.current_hovered_card._hovered = False
                self.current_hovered_card.leaveEvent(None)
            self.current_hovered_card = card
        else:
            # Если мышь покинула карточку
            if self.current_hovered_card == card:
                self.current_hovered_card = None

    def loadGames(self):
        display_filter = read_display_filter()
        favorites = read_favorites()
        self.pending_games = []
        self.games = []
        self.progress_bar.setValue(0)
        self.progress_bar.setVisible(True)
        if display_filter == "steam":
            self._load_steam_games_async(lambda games: self.games_loaded.emit(games))
        elif display_filter == "portproton":
            self._load_portproton_games_async(lambda games: self.games_loaded.emit(games))
        elif display_filter == "epic":
            load_egs_games_async(
                self.legendary_path,
                lambda games: self.games_loaded.emit(games),
                self.downloader,
                self.update_progress.emit,
                self.update_status_message.emit
            )
        elif display_filter == "favorites":
            def on_all_games(portproton_games, steam_games, epic_games):
                games = [game for game in portproton_games + steam_games + epic_games if game[0] in favorites]
                self.games_loaded.emit(games)
            self._load_portproton_games_async(
                lambda pg: self._load_steam_games_async(
                    lambda sg: load_egs_games_async(
                        self.legendary_path,
                        lambda eg: on_all_games(pg, sg, eg),
                        self.downloader,
                        self.update_progress.emit,
                        self.update_status_message.emit
                    )
                )
            )
        else:
            def on_all_games(portproton_games, steam_games, epic_games):
                seen = set()
                games = []
                for game in portproton_games + steam_games + epic_games:
                    # Уникальный ключ: имя + exec_line
                    key = (game[0], game[4])
                    if key not in seen:
                        seen.add(key)
                        games.append(game)
                self.games_loaded.emit(games)
            self._load_portproton_games_async(
                lambda pg: self._load_steam_games_async(
                    lambda sg: load_egs_games_async(
                        self.legendary_path,
                        lambda eg: on_all_games(pg, sg, eg),
                        self.downloader,
                        self.update_progress.emit,
                        self.update_status_message.emit
                    )
                )
            )
        return []

    def _load_steam_games_async(self, callback: Callable[[list[tuple]], None]):
        steam_games = []
        installed_games = get_steam_installed_games()
        logger.info("Found %d installed Steam games: %s", len(installed_games), [g[0] for g in installed_games])
        if not installed_games:
            callback(steam_games)
            return
        self.total_games = len(installed_games)
        self.update_progress.emit(0)  # Initialize progress bar
        self.update_status_message.emit(_("Loading Steam games..."), 3000)
        processed_count = 0

        def on_game_info(info: dict, name, appid, last_played, playtime_seconds):
            nonlocal processed_count
            if not info:
                logger.warning("No info retrieved for game %s (appid %s)", name, appid)
                info = {
                    'description': '',
                    'cover': '',
                    'controller_support': '',
                    'protondb_tier': '',
                    'name': name,
                    'game_source': 'steam'
                }
            last_launch = format_last_launch(datetime.fromtimestamp(last_played)) if last_played else _("Never")
            steam_games.append((
                name,
                info.get('description', ''),
                info.get('cover', ''),
                appid,
                f"steam://rungameid/{appid}",
                info.get('controller_support', ''),
                last_launch,
                format_playtime(playtime_seconds),
                info.get('protondb_tier', ''),
                info.get("anticheat_status", ""),
                last_played,
                playtime_seconds,
                "steam"
            ))
            processed_count += 1
            self.pending_games.append(None)
            self.update_progress.emit(len(self.pending_games))  # Update progress bar
            logger.info("Game %s processed, processed_count: %d/%d", name, processed_count, len(installed_games))
            if processed_count == len(installed_games):
                callback(steam_games)

        for name, appid, last_played, playtime_seconds in installed_games:
            logger.debug("Requesting info for game %s (appid %s)", name, appid)
            get_full_steam_game_info_async(appid, lambda info, n=name, a=appid, lp=last_played, pt=playtime_seconds: on_game_info(info, n, a, lp, pt))

    def _load_portproton_games_async(self, callback: Callable[[list[tuple]], None]):
        games = []
        if not self.portproton_location:
            callback(games)
            return
        desktop_files = [entry.path for entry in os.scandir(self.portproton_location)
                        if entry.name.endswith(".desktop")]
        if not desktop_files:
            callback(games)
            return
        self.total_games = len(desktop_files)
        self.update_progress.emit(0)  # Initialize progress bar
        self.update_status_message.emit(_("Loading PortProton games..."), 3000)
        def on_desktop_processed(result: tuple | None, games=games):
            if result:
                games.append(result)
            self.pending_games.append(None)
            self.update_progress.emit(len(self.pending_games))  # Update progress bar
            if len(self.pending_games) == len(desktop_files):
                callback(games)
        with ThreadPoolExecutor() as executor:
            for file_path in desktop_files:
                executor.submit(self._process_desktop_file_async, file_path, on_desktop_processed)

    def _process_desktop_file_async(self, file_path: str, callback: Callable[[tuple | None], None]):
        entry = parse_desktop_entry(file_path)
        if not entry:
            callback(None)
            return
        desktop_name = entry.get("Name", _("Unknown Game"))
        if desktop_name.lower() in ["portproton", "readme"]:
            callback(None)
            return
        exec_line = entry.get("Exec", "")
        game_exe = ""
        exe_name = ""
        playtime_seconds = 0
        formatted_playtime = ""

        if exec_line:
            parts = shlex.split(exec_line)
            game_exe = os.path.expanduser(parts[3] if len(parts) >= 4 else exec_line)

        repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        builtin_custom_folder = os.path.join(repo_root, "portprotonqt", "custom_data")
        xdg_data_home = os.getenv("XDG_DATA_HOME",
                                os.path.join(os.path.expanduser("~"), ".local", "share"))
        user_custom_folder = os.path.join(xdg_data_home, "PortProtonQt", "custom_data")
        os.makedirs(user_custom_folder, exist_ok=True)

        builtin_cover = ""
        user_cover = ""
        user_game_folder = ""
        builtin_game_folder = ""

        if game_exe:
            exe_name = os.path.splitext(os.path.basename(game_exe))[0]
            builtin_game_folder = os.path.join(builtin_custom_folder, exe_name)
            user_game_folder = os.path.join(user_custom_folder, exe_name)
            os.makedirs(user_game_folder, exist_ok=True)

            # Check if local game folder is empty and download assets if it is
            if not os.listdir(user_game_folder):
                logger.debug(f"Local folder for {exe_name} is empty, checking repository")
                def on_assets_downloaded(results):
                    nonlocal user_cover
                    if results["cover"]:
                        user_cover = results["cover"]
                        logger.info(f"Downloaded assets for {exe_name}: {results}")
                    if results["metadata"]:
                        logger.info(f"Downloaded metadata for {exe_name}: {results['metadata']}")
                self.portproton_api.download_game_assets_async(exe_name, timeout=5, callback=on_assets_downloaded)

            # Read cover
            builtin_files = set(os.listdir(builtin_game_folder)) if os.path.exists(builtin_game_folder) else set()
            for ext in [".jpg", ".png", ".jpeg", ".bmp"]:
                candidate = f"cover{ext}"
                if candidate in builtin_files:
                    builtin_cover = os.path.join(builtin_game_folder, candidate)
                    break

            user_files = set(os.listdir(user_game_folder)) if os.path.exists(user_game_folder) else set()
            for ext in [".jpg", ".png", ".jpeg", ".bmp"]:
                candidate = f"cover{ext}"
                if candidate in user_files:
                    user_cover = os.path.join(user_game_folder, candidate)
                    break

            # Read statistics
            if self.portproton_location:
                statistics_file = os.path.join(self.portproton_location, "data", "tmp", "statistics")
                try:
                    playtime_data = parse_playtime_file(statistics_file)
                    matching_key = next(
                        (key for key in playtime_data if os.path.basename(key).split('.')[0] == exe_name),
                        None
                    )
                    if matching_key:
                        playtime_seconds = playtime_data[matching_key]
                        formatted_playtime = format_playtime(playtime_seconds)
                except Exception as e:
                    logger.error(f"Failed to parse playtime data: {e}")

        def on_steam_info(steam_info: dict):
            # Get current language
            language_code = get_egs_language()

            # Read translations from metadata.txt
            user_metadata_file = os.path.join(user_game_folder, "metadata.txt")
            builtin_metadata_file = os.path.join(builtin_game_folder, "metadata.txt")

            # Try user translations first
            translations = {'name': desktop_name, 'description': ''}
            if os.path.exists(user_metadata_file):
                translations = read_metadata_translations(user_metadata_file, language_code)
            elif os.path.exists(builtin_metadata_file):
                translations = read_metadata_translations(builtin_metadata_file, language_code)

            final_name = translations['name']
            final_desc = translations['description'] or steam_info.get("description", "")
            final_cover = (user_cover if user_cover else
                        builtin_cover if builtin_cover else
                        steam_info.get("cover", "") or entry.get("Icon", ""))

            callback((
                final_name,
                final_desc,
                final_cover,
                steam_info.get("appid", ""),
                exec_line,
                steam_info.get("controller_support", ""),
                get_last_launch(exe_name) if exe_name else _("Never"),
                formatted_playtime,
                steam_info.get("protondb_tier", ""),
                steam_info.get("anticheat_status", ""),
                get_last_launch_timestamp(exe_name) if exe_name else 0,
                playtime_seconds,
                "portproton"
            ))

        get_steam_game_info_async(desktop_name, exec_line, on_steam_info)

    def finalize_game_loading(self):
        logger.info("Finalizing game loading, pending_games: %d", len(self.pending_games))
        if self.pending_games and all(x is None for x in self.pending_games):
            logger.info("All games processed, clearing pending_games")
            self.pending_games = []
            self.update_progress.emit(0)  # Hide progress bar
            self.progress_bar.setVisible(False)
            self.update_status_message.emit("", 0)  # Clear status message

    # ВКЛАДКИ
    def switchTab(self, index):
        """Устанавливает активную вкладку по индексу."""
        for i, btn in self.tabButtons.items():
            btn.setChecked(i == index)
        self.stackedWidget.setCurrentIndex(index)

    def openSystemOverlay(self):
        """Opens the system overlay dialog."""
        overlay = SystemOverlay(self, self.theme)
        overlay.exec()

    def createSearchWidget(self) -> tuple[QWidget, QLineEdit]:
        self.container = QWidget()
        self.container.setStyleSheet(self.theme.CONTAINER_STYLE)
        layout = QHBoxLayout(self.container)
        layout.setContentsMargins(0, 6, 0, 0)
        layout.setSpacing(10)

        self.GameLibraryTitle = QLabel(_("Game Library"))
        self.GameLibraryTitle.setStyleSheet(self.theme.INSTALLED_TAB_TITLE_STYLE)
        layout.addWidget(self.GameLibraryTitle)

        self.addGameButton = AutoSizeButton(_("Add Game"), icon=self.theme_manager.get_icon("addgame"))
        self.addGameButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE)
        self.addGameButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self.addGameButton.clicked.connect(self.openAddGameDialog)
        layout.addWidget(self.addGameButton, alignment=Qt.AlignmentFlag.AlignRight)

        self.searchEdit = CustomLineEdit(self, theme=self.theme)
        icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
        action_pos = cast(QLineEdit.ActionPosition, QLineEdit.ActionPosition.LeadingPosition)
        self.search_action = self.searchEdit.addAction(icon, action_pos)
        self.searchEdit.setMaximumWidth(200)
        self.searchEdit.setPlaceholderText(_("Find Games ..."))
        self.searchEdit.setClearButtonEnabled(True)
        self.searchEdit.setStyleSheet(self.theme.SEARCH_EDIT_STYLE)

        # Добавляем дебансирование для поиска
        self.searchEdit.textChanged.connect(self.startSearchDebounce)
        self.searchDebounceTimer = QTimer(self)
        self.searchDebounceTimer.setSingleShot(True)
        self.searchDebounceTimer.setInterval(300)
        self.searchDebounceTimer.timeout.connect(self.filterGamesDelayed)

        layout.addWidget(self.searchEdit)
        return self.container, self.searchEdit

    def startSearchDebounce(self, text):
        self.searchDebounceTimer.start()

    def on_slider_released(self):
        self.card_width = self.sizeSlider.value()
        self.sizeSlider.setToolTip(f"{self.card_width} px")
        save_card_size(self.card_width)
        for card in self.game_card_cache.values():
            card.update_card_size(self.card_width)
        self.updateGameGrid()

    def filterGamesDelayed(self):
        """Filters games based on search text and updates the grid."""
        text = self.searchEdit.text().strip().lower()
        if text == "":
            self.filtered_games = self.games
        else:
            self.filtered_games = [game for game in self.games if text in game[0].lower()]
        self.updateGameGrid(self.filtered_games)

    def createInstalledTab(self):
        self.gamesLibraryWidget = QWidget()
        self.gamesLibraryWidget.setStyleSheet(self.theme.LIBRARY_WIDGET_STYLE)
        layout = QVBoxLayout(self.gamesLibraryWidget)
        layout.setSpacing(15)

        searchWidget, self.searchEdit = self.createSearchWidget()
        layout.addWidget(searchWidget)

        scrollArea = QScrollArea()
        scrollArea.setWidgetResizable(True)
        scrollArea.setStyleSheet(self.theme.SCROLL_AREA_STYLE)

        self.gamesListWidget = QWidget()
        self.gamesListWidget.setStyleSheet(self.theme.LIST_WIDGET_STYLE)
        self.gamesListLayout = FlowLayout(self.gamesListWidget)
        self.gamesListWidget.setLayout(self.gamesListLayout)

        scrollArea.setWidget(self.gamesListWidget)
        layout.addWidget(scrollArea)

        sliderLayout = QHBoxLayout()
        sliderLayout.addStretch()
        self.sizeSlider = QSlider(Qt.Orientation.Horizontal)
        self.sizeSlider.setMinimum(200)
        self.sizeSlider.setMaximum(250)
        self.sizeSlider.setValue(self.card_width)
        self.sizeSlider.setTickInterval(10)
        self.sizeSlider.setFixedWidth(150)
        self.sizeSlider.setToolTip(f"{self.card_width} px")
        self.sizeSlider.setStyleSheet(self.theme.SLIDER_SIZE_STYLE)
        self.sizeSlider.sliderReleased.connect(self.on_slider_released)
        sliderLayout.addWidget(self.sizeSlider)
        layout.addLayout(sliderLayout)

        def calculate_card_width():
            available_width = scrollArea.width() - 20
            spacing = self.gamesListLayout._spacing
            target_cards_per_row = 8
            calculated_width = (available_width - spacing * (target_cards_per_row - 1)) // target_cards_per_row
            calculated_width = max(200, min(calculated_width, 250))

        QTimer.singleShot(0, calculate_card_width)

        # Добавляем обработчик прокрутки для ленивой загрузки
        scrollArea.verticalScrollBar().valueChanged.connect(self.loadVisibleImages)

        self.stackedWidget.addWidget(self.gamesLibraryWidget)
        self.updateGameGrid()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        if hasattr(self, '_animations') and self._animations:
            for widget, animation in list(self._animations.items()):
                try:
                    if animation.state() == QAbstractAnimation.State.Running:
                        animation.stop()
                        widget.setWindowOpacity(1.0)
                        del self._animations[widget]
                except RuntimeError:
                    del self._animations[widget]
        if not hasattr(self, '_last_width'):
            self._last_width = self.width()
        if abs(self.width() - self._last_width) > 10:
            self._last_width = self.width()

    def loadVisibleImages(self):
        visible_region = self.gamesListWidget.visibleRegion()
        max_concurrent_loads = 5
        loaded_count = 0
        for card_key, card in self.game_card_cache.items():
            if card_key in self.pending_images and visible_region.contains(card.pos()) and loaded_count < max_concurrent_loads:
                cover_path, width, height, callback = self.pending_images.pop(card_key)
                load_pixmap_async(cover_path, width, height, callback)
                loaded_count += 1

    def updateGameGrid(self, games_list=None):
        """Обновляет сетку игровых карточек с сохранением порядка сортировки"""
        # Подготовка данных
        games_list = games_list if games_list is not None else self.games
        search_text = self.searchEdit.text().strip().lower()
        favorites = read_favorites()
        sort_method = read_sort_method()

        # Сортируем игры согласно текущим настройкам
        def sort_key(game):
            name = game[0]
            # Избранные всегда первые
            if name in favorites:
                fav_order = 0
            else:
                fav_order = 1

            if sort_method == "playtime":
                return (fav_order, -game[11], -game[10])  # playtime_seconds, last_launch_ts
            elif sort_method == "alphabetical":
                return (fav_order, name.lower())
            elif sort_method == "favorites":
                return (fav_order,)
            else:  # "last_launch" или по умолчанию
                return (fav_order, -game[10], -game[11])  # last_launch_ts, playtime_seconds

        sorted_games = sorted(games_list, key=sort_key)

        # Создаем временный список для новых карточек
        new_card_order = []

        # Обрабатываем каждую игру в отсортированном порядке
        for game_data in sorted_games:
            game_name = game_data[0]
            exec_line = game_data[4]
            game_key = (game_name, exec_line)
            should_be_visible = not search_text or search_text in game_name.lower()

            # Если карточка уже существует - используем существующую
            if game_key in self.game_card_cache:
                card = self.game_card_cache[game_key]
                card.setVisible(should_be_visible)
                new_card_order.append((game_key, card))
                continue

            # Создаем новую карточку
            card = GameCard(
                *game_data,
                select_callback=self.openGameDetailPage,
                theme=self.theme,
                card_width=self.card_width,
                context_menu_manager=self.context_menu_manager
            )

            # Подключаем сигналы
            card.hoverChanged.connect(self._on_card_hovered)
            card.focusChanged.connect(self._on_card_focused)

            # Подключаем сигналы контекстного меню
            card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
            card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
            card.addToMenuRequested.connect(self.context_menu_manager.add_to_menu)
            card.removeFromMenuRequested.connect(self.context_menu_manager.remove_from_menu)
            card.addToDesktopRequested.connect(self.context_menu_manager.add_to_desktop)
            card.removeFromDesktopRequested.connect(self.context_menu_manager.remove_from_desktop)
            card.addToSteamRequested.connect(self.context_menu_manager.add_to_steam)
            card.removeFromSteamRequested.connect(self.context_menu_manager.remove_from_steam)
            card.openGameFolderRequested.connect(self.context_menu_manager.open_game_folder)

            # Добавляем в кэш и временный список
            self.game_card_cache[game_key] = card
            new_card_order.append((game_key, card))
            card.setVisible(should_be_visible)

        # Полностью перестраиваем макет в правильном порядке, чистим FlowLayout
        while self.gamesListLayout.count():
            child = self.gamesListLayout.takeAt(0)
            if child.widget():
                child.widget().setParent(None)

        # Добавляем карточки в макет в отсортированном порядке
        for _game_key, card in new_card_order:
            self.gamesListLayout.addWidget(card)

            # Загружаем обложку, если карточка видима
            if card.isVisible():
                self.loadVisibleImages()

        # Удаляем карточки для игр, которых больше нет в списке
        existing_keys = {game_key for game_key, _ in new_card_order}
        for card_key in list(self.game_card_cache.keys()):
            if card_key not in existing_keys:
                card = self.game_card_cache.pop(card_key)
                card.deleteLater()
                if card_key in self.pending_images:
                    del self.pending_images[card_key]

        # Принудительно обновляем макет
        self.gamesListLayout.update()
        self.gamesListWidget.updateGeometry()
        self.gamesListWidget.update()

        # Сохраняем текущий размер карточек
        self._last_card_width = self.card_width

    def clearLayout(self, layout):
        """Удаляет все виджеты из layout."""
        while layout.count():
            child = layout.takeAt(0)
            if child.widget():
                widget = child.widget()
                # Remove from game_card_cache if it's a GameCard
                for key, card in list(self.game_card_cache.items()):
                    if card == widget:
                        del self.game_card_cache[key]
                        # Also remove from pending_images if present
                        if key in self.pending_images:
                            del self.pending_images[key]
                widget.deleteLater()

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            for url in event.mimeData().urls():
                if url.toLocalFile().lower().endswith(".exe"):
                    event.acceptProposedAction()
                    return
        event.ignore()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            path = url.toLocalFile()
            if path.lower().endswith(".exe"):
                self.openAddGameDialog(path)
                break

    def openAddGameDialog(self, exe_path=None):
        """Открывает диалоговое окно 'Add Game' с текущей темой."""
        # Проверяем, открыт ли уже диалог
        if self.current_add_game_dialog is not None and self.current_add_game_dialog.isVisible():
            self.current_add_game_dialog.activateWindow()  # Активируем существующий диалог
            self.current_add_game_dialog.raise_()  # Поднимаем окно
            return

        dialog = AddGameDialog(self, self.theme)
        dialog.setFocus(Qt.FocusReason.OtherFocusReason)
        self.current_add_game_dialog = dialog  # Сохраняем ссылку на диалог

        # Предзаполняем путь к .exe при drag-and-drop
        if exe_path:
            dialog.exeEdit.setText(exe_path)
            dialog.nameEdit.setText(os.path.splitext(os.path.basename(exe_path))[0])
            dialog.updatePreview()

        # Обработчик закрытия диалога
        def on_dialog_finished():
            self.current_add_game_dialog = None  # Сбрасываем ссылку при закрытии

        dialog.finished.connect(on_dialog_finished)

        if dialog.exec() == QDialog.DialogCode.Accepted:
            name = dialog.nameEdit.text().strip()
            exe_path = dialog.exeEdit.text().strip()
            user_cover = dialog.coverEdit.text().strip()

            if not name or not exe_path:
                return

            # Сохраняем .desktop файл
            desktop_entry, desktop_path = dialog.getDesktopEntryData()
            if desktop_entry and desktop_path:
                with open(desktop_path, "w", encoding="utf-8") as f:
                    f.write(desktop_entry)
                    os.chmod(desktop_path, 0o755)

                # Проверяем путь обложки, если он отличается от стандартной
                if os.path.isfile(user_cover):
                    exe_name = os.path.splitext(os.path.basename(exe_path))[0]
                    xdg_data_home = os.getenv("XDG_DATA_HOME",
                        os.path.join(os.path.expanduser("~"), ".local", "share"))
                    custom_folder = os.path.join(
                        xdg_data_home,
                        "PortProtonQt",
                        "custom_data",
                        exe_name
                    )
                    os.makedirs(custom_folder, exist_ok=True)

                    # Сохраняем пользовательскую обложку как cover.*
                    ext = os.path.splitext(user_cover)[1].lower()
                    if ext in [".png", ".jpg", ".jpeg", ".bmp"]:
                        shutil.copyfile(user_cover, os.path.join(custom_folder, f"cover{ext}"))

            self.games = self.loadGames()
            self.updateGameGrid()

    def createAutoInstallTab(self):
        """Вкладка 'Auto Install'."""
        self.autoInstallWidget = QWidget()
        self.autoInstallWidget.setStyleSheet(self.theme.OTHER_PAGES_WIDGET_STYLE)
        self.autoInstallWidget.setObjectName("otherPage")
        layout = QVBoxLayout(self.autoInstallWidget)
        layout.setContentsMargins(10, 18, 10, 10)

        self.autoInstallTitle = QLabel(_("Auto Install"))
        self.autoInstallTitle.setStyleSheet(self.theme.TAB_TITLE_STYLE)
        self.autoInstallTitle.setObjectName("tabTitle")
        layout.addWidget(self.autoInstallTitle)

        self.autoInstallContent = QLabel(_("Here you can configure automatic game installation..."))
        self.autoInstallContent.setStyleSheet(self.theme.CONTENT_STYLE)
        self.autoInstallContent.setObjectName("tabContent")
        layout.addWidget(self.autoInstallContent)
        layout.addStretch(1)

        self.stackedWidget.addWidget(self.autoInstallWidget)

    def createEmulatorsTab(self):
        """Вкладка 'Emulators'."""
        self.emulatorsWidget = QWidget()
        self.emulatorsWidget.setStyleSheet(self.theme.OTHER_PAGES_WIDGET_STYLE)
        self.emulatorsWidget.setObjectName("otherPage")
        layout = QVBoxLayout(self.emulatorsWidget)
        layout.setContentsMargins(10, 18, 10, 10)

        self.emulatorsTitle = QLabel(_("Emulators"))
        self.emulatorsTitle.setStyleSheet(self.theme.TAB_TITLE_STYLE)
        self.emulatorsTitle.setObjectName("tabTitle")
        layout.addWidget(self.emulatorsTitle)

        self.emulatorsContent = QLabel(_("List of available emulators and their configuration..."))
        self.emulatorsContent.setStyleSheet(self.theme.CONTENT_STYLE)
        self.emulatorsContent.setObjectName("tabContent")
        layout.addWidget(self.emulatorsContent)
        layout.addStretch(1)

        self.stackedWidget.addWidget(self.emulatorsWidget)

    def createWineTab(self):
        """Вкладка 'Wine Settings'."""
        self.wineWidget = QWidget()
        self.wineWidget.setStyleSheet(self.theme.OTHER_PAGES_WIDGET_STYLE)
        self.wineWidget.setObjectName("otherPage")
        layout = QVBoxLayout(self.wineWidget)
        layout.setContentsMargins(10, 18, 10, 10)

        self.wineTitle = QLabel(_("Wine Settings"))
        self.wineTitle.setStyleSheet(self.theme.TAB_TITLE_STYLE)
        self.wineTitle.setObjectName("tabTitle")
        layout.addWidget(self.wineTitle)

        self.wineContent = QLabel(_("Various Wine parameters and versions..."))
        self.wineContent.setStyleSheet(self.theme.CONTENT_STYLE)
        self.wineContent.setObjectName("tabContent")
        layout.addWidget(self.wineContent)
        layout.addStretch(1)

        self.stackedWidget.addWidget(self.wineWidget)

    def createPortProtonTab(self):
        """Вкладка 'PortProton Settings'."""
        self.portProtonWidget = QWidget()
        self.portProtonWidget.setStyleSheet(self.theme.OTHER_PAGES_WIDGET_STYLE)
        self.portProtonWidget.setObjectName("otherPage")
        layout = QVBoxLayout(self.portProtonWidget)
        layout.setContentsMargins(10, 18, 10, 10)

        # Заголовок
        title = QLabel(_("PortProton Settings"))
        title.setStyleSheet(self.theme.TAB_TITLE_STYLE)
        title.setObjectName("tabTitle")
        title.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        layout.addWidget(title)

        # Подзаголовок/описание
        content = QLabel(_("Main PortProton parameters..."))
        content.setStyleSheet(self.theme.CONTENT_STYLE)
        content.setObjectName("tabContent")
        content.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        layout.addWidget(content)

        # Форма с настройками
        formLayout = QFormLayout()
        formLayout.setContentsMargins(0, 10, 0, 0)
        formLayout.setSpacing(10)
        formLayout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft)

        # 1. Time detail_level
        self.timeDetailCombo = QComboBox()
        self.timeDetailCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        self.time_keys = ["detailed", "brief"]
        self.time_labels = [_("detailed"), _("brief")]
        self.timeDetailCombo.addItems(self.time_labels)
        self.timeDetailCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
        self.timeDetailCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.timeDetailTitle = QLabel(_("Time Detail Level:"))
        self.timeDetailTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.timeDetailTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current = read_time_config()
        try:
            idx = self.time_keys.index(current)
        except ValueError:
            idx = 0
        self.timeDetailCombo.setCurrentIndex(idx)
        formLayout.addRow(self.timeDetailTitle, self.timeDetailCombo)

        # 2. Games sort_method
        self.gamesSortCombo = QComboBox()
        self.gamesSortCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        self.sort_keys = ["last_launch", "playtime", "alphabetical", "favorites"]
        self.sort_labels = [_("last launch"), _("playtime"), _("alphabetical"), _("favorites")]
        self.gamesSortCombo.addItems(self.sort_labels)
        self.gamesSortCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
        self.gamesSortCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.gamesSortTitle = QLabel(_("Games Sort Method:"))
        self.gamesSortTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.gamesSortTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current = read_sort_method()
        try:
            idx = self.sort_keys.index(current)
        except ValueError:
            idx = 0
        self.gamesSortCombo.setCurrentIndex(idx)
        formLayout.addRow(self.gamesSortTitle, self.gamesSortCombo)

        # 3. Games display_filter
        self.filter_keys = ["all", "steam", "portproton", "favorites", "epic"]
        self.filter_labels = [_("all"), "steam", "portproton", _("favorites"), "epic games store"]
        self.gamesDisplayCombo = QComboBox()
        self.gamesDisplayCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
        self.gamesDisplayCombo.addItems(self.filter_labels)
        self.gamesDisplayCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
        self.gamesDisplayCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.gamesDisplayTitle = QLabel(_("Games Display Filter:"))
        self.gamesDisplayTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.gamesDisplayTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current = read_display_filter()
        try:
            idx = self.filter_keys.index(current)
        except ValueError:
            idx = 0
        self.gamesDisplayCombo.setCurrentIndex(idx)
        formLayout.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo)

        # 4. Proxy settings
        self.proxyUrlEdit = CustomLineEdit(self, theme=self.theme)
        self.proxyUrlEdit.setPlaceholderText(_("Proxy URL"))
        self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
        self.proxyUrlEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.proxyUrlTitle = QLabel(_("Proxy URL:"))
        self.proxyUrlTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.proxyUrlTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        proxy_cfg = read_proxy_config()
        if proxy_cfg.get("http", ""):
            self.proxyUrlEdit.setText(proxy_cfg["http"])
        formLayout.addRow(self.proxyUrlTitle, self.proxyUrlEdit)

        self.proxyUserEdit = CustomLineEdit(self, theme=self.theme)
        self.proxyUserEdit.setPlaceholderText(_("Proxy Username"))
        self.proxyUserEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
        self.proxyUserEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.proxyUserTitle = QLabel(_("Proxy Username:"))
        self.proxyUserTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.proxyUserTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        formLayout.addRow(self.proxyUserTitle, self.proxyUserEdit)

        self.proxyPasswordEdit = CustomLineEdit(self, theme=self.theme)
        self.proxyPasswordEdit.setPlaceholderText(_("Proxy Password"))
        self.proxyPasswordEdit.setEchoMode(QLineEdit.EchoMode.Password)
        self.proxyPasswordEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
        self.proxyPasswordEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.proxyPasswordTitle = QLabel(_("Proxy Password:"))
        self.proxyPasswordTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.proxyPasswordTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        formLayout.addRow(self.proxyPasswordTitle, self.proxyPasswordEdit)

        # 5. Fullscreen setting for application
        self.fullscreenCheckBox = QCheckBox(_("Launch Application in Fullscreen"))
        self.fullscreenCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
        self.fullscreenCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.fullscreenTitle = QLabel(_("Application Fullscreen Mode:"))
        self.fullscreenTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.fullscreenTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current_fullscreen = read_fullscreen_config()
        self.fullscreenCheckBox.setChecked(current_fullscreen)
        formLayout.addRow(self.fullscreenTitle, self.fullscreenCheckBox)

        # 6. Automatic fullscreen on gamepad connection
        self.autoFullscreenGamepadCheckBox = QCheckBox(_("Auto Fullscreen on Gamepad connected"))
        self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
        self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
        self.autoFullscreenGamepadTitle = QLabel(_("Auto Fullscreen on Gamepad connected:"))
        self.autoFullscreenGamepadTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.autoFullscreenGamepadTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current_auto_fullscreen = read_auto_fullscreen_gamepad()
        self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen)
        formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox)

        # 7. Gamepad haptic feedback config
        self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback"))
        self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
        self.gamepadRumbleTitle = QLabel(_("Gamepad haptic feedback:"))
        self.gamepadRumbleTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.gamepadRumbleTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        current_rumble_state = read_rumble_config()
        self.gamepadRumbleCheckBox.setChecked(current_rumble_state)
        formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox)

        # 8. Legendary Authentication
        self.legendaryAuthButton = AutoSizeButton(
            _("Open Legendary Login"),
            icon=self.theme_manager.get_icon("login")
        )
        self.legendaryAuthButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.legendaryAuthButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.legendaryAuthButton.clicked.connect(self.openLegendaryLogin)
        self.legendaryAuthTitle = QLabel(_("Legendary Authentication:"))
        self.legendaryAuthTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.legendaryAuthTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        formLayout.addRow(self.legendaryAuthTitle, self.legendaryAuthButton)

        self.legendaryCodeEdit = CustomLineEdit(self, theme=self.theme)
        self.legendaryCodeEdit.setPlaceholderText(_("Enter Legendary Authorization Code"))
        self.legendaryCodeEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
        self.legendaryCodeEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.legendaryCodeTitle = QLabel(_("Authorization Code:"))
        self.legendaryCodeTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
        self.legendaryCodeTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        formLayout.addRow(self.legendaryCodeTitle, self.legendaryCodeEdit)

        self.submitCodeButton = AutoSizeButton(
            _("Submit Code"),
            icon=self.theme_manager.get_icon("save")
        )
        self.submitCodeButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.submitCodeButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.submitCodeButton.clicked.connect(self.submitLegendaryCode)
        formLayout.addRow(QLabel(""), self.submitCodeButton)

        layout.addLayout(formLayout)

        # Кнопки
        buttonsLayout = QHBoxLayout()
        buttonsLayout.setSpacing(10)

        # Кнопка сохранения настроек
        self.saveButton = AutoSizeButton(
            _("Save Settings"),
            icon=self.theme_manager.get_icon("save")
        )
        self.saveButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.saveButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.saveButton.clicked.connect(self.savePortProtonSettings)
        buttonsLayout.addWidget(self.saveButton)

        # Кнопка сброса настроек
        self.resetSettingsButton = AutoSizeButton(
            _("Reset Settings"),
            icon=self.theme_manager.get_icon("update")
        )
        self.resetSettingsButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.resetSettingsButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.resetSettingsButton.clicked.connect(self.resetSettings)
        buttonsLayout.addWidget(self.resetSettingsButton)

        # Кнопка очистки кэша
        self.clearCacheButton = AutoSizeButton(
            _("Clear Cache"),
            icon=self.theme_manager.get_icon("update")
        )
        self.clearCacheButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.clearCacheButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.clearCacheButton.clicked.connect(self.clearCache)
        buttonsLayout.addWidget(self.clearCacheButton)

        layout.addLayout(buttonsLayout)
        layout.addStretch(1)
        self.stackedWidget.addWidget(self.portProtonWidget)

    def openLegendaryLogin(self):
        """Opens the Legendary login page in the default web browser."""
        login_url = "https://legendary.gl/epiclogin"
        try:
            QDesktopServices.openUrl(QUrl(login_url))
            self.statusBar().showMessage(_("Opened Legendary login page in browser"), 3000)
        except Exception as e:
            logger.error(f"Failed to open Legendary login page: {e}")
            self.statusBar().showMessage(_("Failed to open Legendary login page"), 3000)

    def submitLegendaryCode(self):
        """Submits the Legendary authorization code using the legendary CLI."""
        auth_code = self.legendaryCodeEdit.text().strip()
        if not auth_code:
            QMessageBox.warning(self, _("Error"), _("Please enter an authorization code"))
            return

        try:
            # Execute legendary auth command
            result = subprocess.run(
                [self.legendary_path, "auth", "--code", auth_code],
                capture_output=True,
                text=True,
                check=True
            )
            logger.info("Legendary authentication successful: %s", result.stdout)
            self.statusBar().showMessage(_("Successfully authenticated with Legendary"), 3000)
            self.legendaryCodeEdit.clear()
            # Reload Epic Games Store games after successful authentication
            self.games = self.loadGames()
            self.updateGameGrid()
        except subprocess.CalledProcessError as e:
            logger.error("Legendary authentication failed: %s", e.stderr)
            self.statusBar().showMessage(_("Legendary authentication failed: {0}").format(e.stderr), 5000)
        except FileNotFoundError:
            logger.error("Legendary executable not found at %s", self.legendary_path)
            self.statusBar().showMessage(_("Legendary executable not found"), 5000)
        except Exception as e:
            logger.error("Unexpected error during Legendary authentication: %s", str(e))
            self.statusBar().showMessage(_("Unexpected error during authentication"), 5000)

    def resetSettings(self):
        """Сбрасывает настройки и перезапускает приложение."""
        reply = QMessageBox.question(
            self,
            _("Confirm Reset"),
            _("Are you sure you want to reset all settings? This action cannot be undone."),
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            reset_config()

            # Показываем сообщение
            self.statusBar().showMessage(_("Settings reset. Restarting..."), 3000)

            # Перезапускаем приложение
            QTimer.singleShot(1000, lambda: self.restart_application())

    def clearCache(self):
        """Очищает кэш."""
        reply = QMessageBox.question(
            self,
            _("Confirm Clear Cache"),
            _("Are you sure you want to clear the cache? This action cannot be undone."),
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            clear_cache()

            # Показываем сообщение
            self.statusBar().showMessage(_("Cache cleared"), 3000)

    def applySettingsDelayed(self):
        """Applies settings with the new filter and updates the game list."""
        read_time_config()
        self.games = []
        self.loadGames()
        display_filter = read_display_filter()
        for card in self.game_card_cache.values():
            card.update_badge_visibility(display_filter)

    def savePortProtonSettings(self):
        """
        Сохраняет параметры конфигурации в конфигурационный файл.
        """
        time_idx = self.timeDetailCombo.currentIndex()
        time_key = self.time_keys[time_idx]
        save_time_config(time_key)

        sort_idx = self.gamesSortCombo.currentIndex()
        sort_key = self.sort_keys[sort_idx]
        save_sort_method(sort_key)

        filter_idx = self.gamesDisplayCombo.currentIndex()
        filter_key = self.filter_keys[filter_idx]
        save_display_filter(filter_key)

        # Сохранение proxy настроек
        proxy_url = self.proxyUrlEdit.text().strip()
        proxy_user = self.proxyUserEdit.text().strip()
        proxy_password = self.proxyPasswordEdit.text().strip()
        save_proxy_config(proxy_url, proxy_user, proxy_password)

        fullscreen = self.fullscreenCheckBox.isChecked()
        save_fullscreen_config(fullscreen)

        auto_fullscreen_gamepad = self.autoFullscreenGamepadCheckBox.isChecked()
        save_auto_fullscreen_gamepad(auto_fullscreen_gamepad)

        # Сохранение настройки виброотдачи геймпада
        rumble_enabled = self.gamepadRumbleCheckBox.isChecked()
        save_rumble_config(rumble_enabled)

        for card in self.game_card_cache.values():
            card.update_badge_visibility(filter_key)

        if self.currentDetailPage and self.current_exec_line:
            current_game = next((game for game in self.games if game[4] == self.current_exec_line), None)
            if current_game:
                self.stackedWidget.removeWidget(self.currentDetailPage)
                self.currentDetailPage.deleteLater()
                self.currentDetailPage = None
                self.openGameDetailPage(*current_game)

        self.settingsDebounceTimer.start()


        # Управление полноэкранным режимом
        gamepad_connected = self.input_manager.find_gamepad() is not None
        if fullscreen or (auto_fullscreen_gamepad and gamepad_connected):
            self.showFullScreen()
        else:
            # Если обе галочки сняты и геймпад не подключен, возвращаем нормальное состояние
            self.showNormal()
            self.resize(*read_window_geometry())  # Восстанавливаем сохраненные размеры окна

        self.statusBar().showMessage(_("Settings saved"), 3000)

    def createThemeTab(self):
        """Вкладка 'Themes'"""
        self.themeTabWidget = QWidget()
        self.themeTabWidget.setStyleSheet(self.theme.OTHER_PAGES_WIDGET_STYLE)
        self.themeTabWidget.setObjectName("otherPage")
        mainLayout = QVBoxLayout(self.themeTabWidget)
        mainLayout.setContentsMargins(10, 14, 10, 10)
        mainLayout.setSpacing(10)

        # 1. Верхняя строка: Заголовок и список тем
        self.themeTabHeaderLayout = QHBoxLayout()

        self.themeTabTitleLabel = QLabel(_("Select Theme:"))
        self.themeTabTitleLabel.setObjectName("tabTitle")
        self.themeTabTitleLabel.setStyleSheet(self.theme.TAB_TITLE_STYLE)
        self.themeTabHeaderLayout.addWidget(self.themeTabTitleLabel)

        self.themesCombo = QComboBox()
        self.themesCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
        self.themesCombo.setObjectName("comboString")
        available_themes = self.theme_manager.get_available_themes()
        if self.current_theme_name in available_themes:
            available_themes.remove(self.current_theme_name)
            available_themes.insert(0, self.current_theme_name)
        self.themesCombo.addItems(available_themes)
        self.themeTabHeaderLayout.addWidget(self.themesCombo)
        self.themeTabHeaderLayout.addStretch(1)

        mainLayout.addLayout(self.themeTabHeaderLayout)

        # 2. Карусель скриншотов
        self.screenshotsCarousel = ImageCarousel([])
        self.screenshotsCarousel.setStyleSheet(self.theme.CAROUSEL_WIDGET_STYLE)
        mainLayout.addWidget(self.screenshotsCarousel, stretch=1)

        # 3. Информация о теме
        self.themeInfoLayout = QVBoxLayout()
        self.themeInfoLayout.setSpacing(10)

        self.themeMetainfoLabel = QLabel()
        self.themeMetainfoLabel.setWordWrap(True)
        self.themeInfoLayout.addWidget(self.themeMetainfoLabel)

        self.applyButton = AutoSizeButton(_("Apply Theme"), icon=self.theme_manager.get_icon("apply"))
        self.applyButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
        self.applyButton.setObjectName("actionButton")
        self.themeInfoLayout.addWidget(self.applyButton)

        mainLayout.addLayout(self.themeInfoLayout)

        # Функция обновления превью
        def updateThemePreview(theme_name):
            meta = load_theme_metainfo(theme_name)
            link = meta.get("author_link", "")
            link_html = f'<a href="{link}">{link}</a>' if link else _("No link")
            unknown_author = _("Unknown")

            preview_text = (
                "<b>" + _("Name:") + "</b> " + meta.get('name', theme_name) + "<br>" +
                "<b>" + _("Description:") + "</b> " + meta.get('description', '') + "<br>" +
                "<b>" + _("Author:") + "</b> " + meta.get('author', unknown_author) + "<br>" +
                "<b>" + _("Link:") + "</b> " + link_html
            )
            self.themeMetainfoLabel.setText(preview_text)
            self.themeMetainfoLabel.setStyleSheet(self.theme.CONTENT_STYLE)
            self.themeMetainfoLabel.setFocusPolicy(Qt.FocusPolicy.NoFocus)

            screenshots = load_theme_screenshots(theme_name)
            if screenshots:
                self.screenshotsCarousel.update_images([
                    (pixmap, os.path.splitext(filename)[0])
                    for pixmap, filename in screenshots
                ])
                self.screenshotsCarousel.show()
            else:
                self.screenshotsCarousel.hide()

        updateThemePreview(self.current_theme_name)
        self.themesCombo.currentTextChanged.connect(updateThemePreview)

        # Логика применения темы
        def on_apply():
            selected_theme = self.themesCombo.currentText()
            if selected_theme:
                theme_module = self.theme_manager.apply_theme(selected_theme)
                if theme_module:
                    save_theme_to_config(selected_theme)
                    self.statusBar().showMessage(_("Theme '{0}' applied successfully").format(selected_theme), 3000)
                    xdg_data_home = os.getenv("XDG_DATA_HOME",
                                            os.path.join(os.path.expanduser("~"), ".local", "share"))
                    state_file = os.path.join(xdg_data_home, "PortProtonQt", "state.txt")
                    os.makedirs(os.path.dirname(state_file), exist_ok=True)
                    try:
                        with open(state_file, "w", encoding="utf-8") as f:
                            f.write("theme_tab\n")
                        logger.info(f"State saved to {state_file}")
                        QTimer.singleShot(500, lambda: self.restart_application())
                    except Exception as e:
                        logger.error(f"Failed to save state to {state_file}: {e}")
                else:
                    self.statusBar().showMessage(_("Error applying theme '{0}'").format(selected_theme), 3000)

        self.applyButton.clicked.connect(on_apply)

        # Добавляем виджет в stackedWidget
        self.stackedWidget.addWidget(self.themeTabWidget)

    def restart_application(self):
        """Перезапускает приложение."""
        if not self.isFullScreen():
            save_window_geometry(self.width(), self.height())
        python = sys.executable
        os.execl(python, python, *sys.argv)

    def restore_state(self):
        """Восстанавливает состояние приложения после перезапуска."""
        xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
        state_file = os.path.join(xdg_data_home, "PortProtonQt", "state.txt")
        logger.info(f"Checking for state file: {state_file}")
        if os.path.exists(state_file):
            try:
                with open(state_file, encoding="utf-8") as f:
                    state = f.read().strip()
                    logger.info(f"State file contents: '{state}'")
                    if state == "theme_tab":
                        logger.info("Restoring to theme tab (index 5)")
                        if self.stackedWidget.count() > 5:
                            self.switchTab(5)
                        else:
                            logger.warning("Theme tab (index 5) not available yet")
                    else:
                        logger.warning(f"Unexpected state value: '{state}'")
                os.remove(state_file)
                logger.info(f"State file {state_file} removed")
            except Exception as e:
                logger.error(f"Failed to read or process state file {state_file}: {e}")
        else:
            logger.info(f"State file {state_file} does not exist")

    # ЛОГИКА ДЕТАЛЬНОЙ СТРАНИЦЫ ИГРЫ
    def getColorPalette_async(self, cover_path, num_colors=5, sample_step=10, callback=None):
        def on_pixmap(pixmap):
            if pixmap.isNull():
                if callback:
                    callback([QColor("#1a1a1a")] * num_colors)
                    return

            image = pixmap.toImage()
            width, height = image.width(), image.height()
            histogram = {}
            for x in range(0, width, sample_step):
                for y in range(0, height, sample_step):
                    color = image.pixelColor(x, y)
                    key = (color.red() // 32, color.green() // 32, color.blue() // 32)
                    if key in histogram:
                        histogram[key][0] += color.red()
                        histogram[key][1] += color.green()
                        histogram[key][2] += color.blue()
                        histogram[key][3] += 1
                    else:
                        histogram[key] = [color.red(), color.green(), color.blue(), 1]
            avg_colors = []
            for _unused, (r_sum, g_sum, b_sum, count) in histogram.items():
                avg_r = r_sum // count
                avg_g = g_sum // count
                avg_b = b_sum // count
                avg_colors.append((count, QColor(avg_r, avg_g, avg_b)))
            avg_colors.sort(key=lambda x: x[0], reverse=True)
            palette = [color for count, color in avg_colors[:num_colors]]
            if len(palette) < num_colors:
                palette += [palette[-1]] * (num_colors - len(palette))
            if callback:
                callback(palette)

        load_pixmap_async(cover_path, 180, 250, on_pixmap)

    def darkenColor(self, color, factor=200):
        return color.darker(factor)

    def openGameDetailPage(self, name, description, cover_path=None, appid="", exec_line="", controller_support="", last_launch="", formatted_playtime="", protondb_tier="", game_source="", anticheat_status=""):
        detailPage = QWidget()
        self._animations = {}
        imageLabel = QLabel()
        imageLabel.setFixedSize(300, 400)
        self._detail_page_active = True
        self._current_detail_page = detailPage

        # Функция загрузки изображения и обновления стилей
        def load_image_and_restore_effect():
            if not detailPage or detailPage.isHidden():
                logger.warning("Detail page is None or hidden, skipping image load")
                return

            detailPage.setWindowOpacity(1.0)

            if cover_path:
                def on_pixmap_ready(pixmap):
                    if not detailPage or detailPage.isHidden():
                        logger.warning("Detail page is None or hidden, skipping pixmap update")
                        return
                    rounded = round_corners(pixmap, 10)
                    imageLabel.setPixmap(rounded)
                    logger.debug("Pixmap set for imageLabel")

                    def on_palette_ready(palette):
                        if not detailPage or detailPage.isHidden():
                            logger.warning("Detail page is None or hidden, skipping palette update")
                            return
                        dark_palette = [self.darkenColor(color, factor=200) for color in palette]
                        stops = ",\n".join(
                            [f"stop:{i/(len(dark_palette)-1):.2f} {dark_palette[i].name()}" for i in range(len(dark_palette))]
                        )
                        detailPage.setStyleSheet(self.theme.detail_page_style(stops))
                        detailPage.update()
                        logger.debug("Stylesheet updated with palette")

                    self.getColorPalette_async(cover_path, num_colors=5, callback=on_palette_ready)
                load_pixmap_async(cover_path, 300, 400, on_pixmap_ready)
            else:
                detailPage.setStyleSheet(self.theme.DETAIL_PAGE_NO_COVER_STYLE)
                detailPage.update()

        def cleanup_animation():
            if detailPage in self._animations:
                del self._animations[detailPage]

        mainLayout = QVBoxLayout(detailPage)
        mainLayout.setContentsMargins(30, 30, 30, 30)
        mainLayout.setSpacing(20)

        backButton = AutoSizeButton(_("Back"), icon=self.theme_manager.get_icon("back"))
        backButton.setFixedWidth(100)
        backButton.setStyleSheet(self.theme.ADDGAME_BACK_BUTTON_STYLE)
        backButton.clicked.connect(lambda: self.goBackDetailPage(detailPage))
        mainLayout.addWidget(backButton, alignment=Qt.AlignmentFlag.AlignLeft)

        contentFrame = QFrame()
        contentFrame.setStyleSheet(self.theme.DETAIL_CONTENT_FRAME_STYLE)
        contentFrameLayout = QHBoxLayout(contentFrame)
        contentFrameLayout.setContentsMargins(20, 20, 20, 20)
        contentFrameLayout.setSpacing(40)
        mainLayout.addWidget(contentFrame)

        # Обложка (слева)
        coverFrame = QFrame()
        coverFrame.setFixedSize(300, 400)
        coverFrame.setStyleSheet(self.theme.COVER_FRAME_STYLE)
        shadow = QGraphicsDropShadowEffect(coverFrame)
        shadow.setBlurRadius(20)
        shadow.setColor(QColor(0, 0, 0, 200))
        shadow.setOffset(0, 0)
        coverFrame.setGraphicsEffect(shadow)
        coverLayout = QVBoxLayout(coverFrame)
        coverLayout.setContentsMargins(0, 0, 0, 0)

        coverLayout.addWidget(imageLabel)

        # Значок избранного
        favoriteLabelCover = ClickableLabel(coverFrame)
        favoriteLabelCover.setFixedSize(*self.theme.favoriteLabelSize)
        favoriteLabelCover.setStyleSheet(self.theme.FAVORITE_LABEL_STYLE)
        favorites = read_favorites()
        if name in favorites:
            favoriteLabelCover.setText("★")
        else:
            favoriteLabelCover.setText("☆")
        favoriteLabelCover.clicked.connect(lambda: self.toggleFavoriteInDetailPage(name, favoriteLabelCover))
        favoriteLabelCover.move(8, 8)
        favoriteLabelCover.raise_()

        # Добавляем бейджи (ProtonDB, Steam, PortProton, WeAntiCheatYet)
        display_filter = read_display_filter()
        steam_visible = (str(game_source).lower() == "steam" and display_filter in ("all", "favorites"))
        egs_visible = (str(game_source).lower() == "epic" and display_filter in ("all", "favorites"))
        portproton_visible = (str(game_source).lower() == "portproton" and display_filter in ("all", "favorites"))
        right_margin = 8
        badge_spacing = 5
        top_y = 10
        badge_y_positions = []
        badge_width = int(300 * 2/3)

        # ProtonDB бейдж
        protondb_text = GameCard.getProtonDBText(protondb_tier)
        if protondb_text:
            icon_filename = GameCard.getProtonDBIconFilename(protondb_tier)
            icon = self.theme_manager.get_icon(icon_filename, self.current_theme_name)
            protondbLabel = ClickableLabel(
                protondb_text,
                icon=icon,
                parent=coverFrame,
                icon_size=16,
                icon_space=3,
            )
            protondbLabel.setStyleSheet(self.theme.get_protondb_badge_style(protondb_tier))
            protondbLabel.setFixedWidth(badge_width)
            protondbLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://www.protondb.com/app/{appid}")))
            protondb_visible = True
        else:
            protondbLabel = ClickableLabel("", parent=coverFrame, icon_size=16, icon_space=3)
            protondbLabel.setFixedWidth(badge_width)
            protondbLabel.setVisible(False)
            protondb_visible = False

        # Steam бейдж
        steam_icon = self.theme_manager.get_icon("steam")
        steamLabel = ClickableLabel(
            "Steam",
            icon=steam_icon,
            parent=coverFrame,
            icon_size=16,
            icon_space=5,
        )
        steamLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        steamLabel.setFixedWidth(badge_width)
        steamLabel.setVisible(steam_visible)
        steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}")))

        # Epic Games Store бейдж
        egs_icon = self.theme_manager.get_icon("epic_games")
        egsLabel = ClickableLabel(
            "Epic Games",
            icon=egs_icon,
            parent=coverFrame,
            icon_size=16,
            icon_space=5,
            change_cursor=False
        )
        egsLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        egsLabel.setFixedWidth(badge_width)
        egsLabel.setVisible(egs_visible)

        # PortProton badge
        portproton_icon = self.theme_manager.get_icon("portproton")
        portprotonLabel = ClickableLabel(
            "PortProton",
            icon=portproton_icon,
            parent=coverFrame,
            icon_size=16,
            icon_space=5,
        )
        portprotonLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
        portprotonLabel.setFixedWidth(badge_width)
        portprotonLabel.setVisible(portproton_visible)
        portprotonLabel.clicked.connect(lambda: self.open_portproton_forum_topic(name))

        # WeAntiCheatYet бейдж
        anticheat_text = GameCard.getAntiCheatText(anticheat_status)
        if anticheat_text:
            icon_filename = GameCard.getAntiCheatIconFilename(anticheat_status)
            icon = self.theme_manager.get_icon(icon_filename, self.current_theme_name)
            anticheatLabel = ClickableLabel(
                anticheat_text,
                icon=icon,
                parent=coverFrame,
                icon_size=16,
                icon_space=3,
            )
            anticheatLabel.setStyleSheet(self.theme.get_anticheat_badge_style(anticheat_status))
            anticheatLabel.setFixedWidth(badge_width)
            anticheatLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://areweanticheatyet.com/game/{name.lower().replace(' ', '-')}")))
            anticheat_visible = True
        else:
            anticheatLabel = ClickableLabel("", parent=coverFrame, icon_size=16, icon_space=3)
            anticheatLabel.setFixedWidth(badge_width)
            anticheatLabel.setVisible(False)
            anticheat_visible = False

        # Расположение бейджей
        if steam_visible:
            steam_x = 300 - badge_width - right_margin
            steamLabel.move(steam_x, top_y)
            badge_y_positions.append(top_y + steamLabel.height())
        if egs_visible:
            egs_x = 300 - badge_width - right_margin
            egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            egsLabel.move(egs_x, egs_y)
            badge_y_positions.append(egs_y + egsLabel.height())
        if portproton_visible:
            portproton_x = 300 - badge_width - right_margin
            portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            portprotonLabel.move(portproton_x, portproton_y)
            badge_y_positions.append(portproton_y + portprotonLabel.height())
        if protondb_visible:
            protondb_x = 300 - badge_width - right_margin
            protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            protondbLabel.move(protondb_x, protondb_y)
            badge_y_positions.append(protondb_y + protondbLabel.height())
        if anticheat_visible:
            anticheat_x = 300 - badge_width - right_margin
            anticheat_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
            anticheatLabel.move(anticheat_x, anticheat_y)

        anticheatLabel.raise_()
        protondbLabel.raise_()
        portprotonLabel.raise_()
        egsLabel.raise_()
        steamLabel.raise_()

        contentFrameLayout.addWidget(coverFrame)

        # Детали игры (справа)
        detailsWidget = QWidget()
        detailsWidget.setStyleSheet(self.theme.DETAILS_WIDGET_STYLE)
        detailsLayout = QVBoxLayout(detailsWidget)
        detailsLayout.setContentsMargins(20, 20, 20, 20)
        detailsLayout.setSpacing(15)

        titleLabel = QLabel(name)
        titleLabel.setStyleSheet(self.theme.DETAIL_PAGE_TITLE_STYLE)
        detailsLayout.addWidget(titleLabel)

        line = QFrame()
        line.setFrameShape(QFrame.Shape.HLine)
        line.setStyleSheet(self.theme.DETAIL_PAGE_LINE_STYLE)
        detailsLayout.addWidget(line)

        descLabel = QLabel(description)
        descLabel.setWordWrap(True)
        descLabel.setStyleSheet(self.theme.DETAIL_PAGE_DESC_STYLE)
        detailsLayout.addWidget(descLabel)

        # Инициализация HowLongToBeat
        hltb = HowLongToBeat(parent=self)

        # Создаем общий layout для всей игровой информации
        gameInfoLayout = QVBoxLayout()
        gameInfoLayout.setSpacing(10)

        # Первая строка: Last Launch и Play Time
        firstRowLayout = QHBoxLayout()
        firstRowLayout.setSpacing(10)

        # Last Launch
        lastLaunchTitle = QLabel(_("LAST LAUNCH"))
        lastLaunchTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
        lastLaunchValue = QLabel(last_launch)
        lastLaunchValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
        firstRowLayout.addWidget(lastLaunchTitle)
        firstRowLayout.addWidget(lastLaunchValue)
        firstRowLayout.addSpacing(30)

        # Play Time
        playTimeTitle = QLabel(_("PLAY TIME"))
        playTimeTitle.setStyleSheet(self.theme.PLAY_TIME_TITLE_STYLE)
        playTimeValue = QLabel(formatted_playtime)
        playTimeValue.setStyleSheet(self.theme.PLAY_TIME_VALUE_STYLE)
        firstRowLayout.addWidget(playTimeTitle)
        firstRowLayout.addWidget(playTimeValue)

        gameInfoLayout.addLayout(firstRowLayout)

        # Создаем placeholder для второй строки (HLTB данные)
        hltbLayout = QHBoxLayout()
        hltbLayout.setSpacing(10)

        # Время прохождения (Main Story, Main + Sides, Completionist)
        def on_hltb_results(results):
            if not hasattr(self, '_detail_page_active') or not self._detail_page_active:
                return
            if not self._current_detail_page or self._current_detail_page.isHidden() or not self._current_detail_page.parent():
                return

            if results:
                game = results[0]  # Берем первый результат
                main_story_time = hltb.format_game_time(game, "main_story")
                main_extra_time = hltb.format_game_time(game, "main_extra")
                completionist_time = hltb.format_game_time(game, "completionist")

                # Очищаем layout перед добавлением новых элементов
                while hltbLayout.count():
                    child = hltbLayout.takeAt(0)
                    if child.widget():
                        child.widget().deleteLater()

                has_data = False

                if main_story_time is not None:
                    mainStoryTitle = QLabel(_("MAIN STORY"))
                    mainStoryTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
                    mainStoryValue = QLabel(main_story_time)
                    mainStoryValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
                    hltbLayout.addWidget(mainStoryTitle)
                    hltbLayout.addWidget(mainStoryValue)
                    hltbLayout.addSpacing(30)
                    has_data = True

                if main_extra_time is not None:
                    mainExtraTitle = QLabel(_("MAIN + SIDES"))
                    mainExtraTitle.setStyleSheet(self.theme.PLAY_TIME_TITLE_STYLE)
                    mainExtraValue = QLabel(main_extra_time)
                    mainExtraValue.setStyleSheet(self.theme.PLAY_TIME_VALUE_STYLE)
                    hltbLayout.addWidget(mainExtraTitle)
                    hltbLayout.addWidget(mainExtraValue)
                    hltbLayout.addSpacing(30)
                    has_data = True

                if completionist_time is not None:
                    completionistTitle = QLabel(_("COMPLETIONIST"))
                    completionistTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
                    completionistValue = QLabel(completionist_time)
                    completionistValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
                    hltbLayout.addWidget(completionistTitle)
                    hltbLayout.addWidget(completionistValue)
                    has_data = True

                # Если есть данные, добавляем layout во вторую строку
                if has_data:
                    gameInfoLayout.addLayout(hltbLayout)

        # Подключаем сигнал searchCompleted к on_hltb_results
        hltb.searchCompleted.connect(on_hltb_results)

        # Запускаем поиск в фоновом потоке
        hltb.search_with_callback(name, case_sensitive=False)

        # Добавляем общий layout с игровой информацией
        detailsLayout.addLayout(gameInfoLayout)

        if controller_support:
            cs = controller_support.lower()
            translated_cs = ""
            if cs == "full":
                translated_cs = _("full")
            elif cs == "partial":
                translated_cs = _("partial")
            elif cs == "none":
                translated_cs = _("none")
            gamepadSupportLabel = QLabel(_("Gamepad Support: {0}").format(translated_cs))
            gamepadSupportLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
            gamepadSupportLabel.setStyleSheet(self.theme.GAMEPAD_SUPPORT_VALUE_STYLE)
            detailsLayout.addWidget(gamepadSupportLabel, alignment=Qt.AlignmentFlag.AlignCenter)

        detailsLayout.addStretch(1)

        # Определяем текущий идентификатор игры по exec_line
        entry_exec_split = shlex.split(exec_line)
        if not entry_exec_split:
            return

        if entry_exec_split[0] == "env":
            file_to_check = entry_exec_split[2] if len(entry_exec_split) >= 3 else None
        elif entry_exec_split[0] == "flatpak":
            file_to_check = entry_exec_split[3] if len(entry_exec_split) >= 4 else None
        else:
            file_to_check = entry_exec_split[0]
        current_exe = os.path.basename(file_to_check) if file_to_check else None

        if self.target_exe is not None and current_exe == self.target_exe:
            playButton = AutoSizeButton(_("Stop"), icon=self.theme_manager.get_icon("stop"))
        else:
            playButton = AutoSizeButton(_("Play"), icon=self.theme_manager.get_icon("play"))

        playButton.setFixedSize(120, 40)
        playButton.setStyleSheet(self.theme.PLAY_BUTTON_STYLE)
        playButton.clicked.connect(lambda: self.toggleGame(exec_line, playButton))
        detailsLayout.addWidget(playButton, alignment=Qt.AlignmentFlag.AlignLeft)

        contentFrameLayout.addWidget(detailsWidget)
        mainLayout.addStretch()

        self.stackedWidget.addWidget(detailPage)
        self.stackedWidget.setCurrentWidget(detailPage)
        self.currentDetailPage = detailPage
        self.current_exec_line = exec_line
        self.current_play_button = playButton

        # Анимация
        self.detail_animations.animate_detail_page(detailPage, load_image_and_restore_effect, cleanup_animation)

    def toggleFavoriteInDetailPage(self, game_name, label):
        favorites = read_favorites()
        if game_name in favorites:
            favorites.remove(game_name)
            label.setText("☆")
        else:
            favorites.append(game_name)
            label.setText("★")
        save_favorites(favorites)
        self.updateGameGrid()

    def activateFocusedWidget(self):
        """Activate the currently focused widget."""
        focused_widget = QApplication.focusWidget()
        if not focused_widget:
            return
        if isinstance(focused_widget, ClickableLabel):
            focused_widget.clicked.emit()
        elif isinstance(focused_widget, AutoSizeButton):
            focused_widget.click()
        elif isinstance(focused_widget, QPushButton):
            focused_widget.click()
        elif isinstance(focused_widget, NavLabel):
            focused_widget.clicked.emit()
        elif isinstance(focused_widget, ImageCarousel):
            if focused_widget.image_items:
                current_item = focused_widget.image_items[focused_widget.horizontalScrollBar().value() // 100]
                current_item.show_fullscreen()
        elif isinstance(focused_widget, QLineEdit):
            focused_widget.setFocus()
            focused_widget.selectAll()
        elif isinstance(focused_widget, QCheckBox):
            focused_widget.setChecked(not focused_widget.isChecked())
        elif isinstance(focused_widget, GameCard):
                    focused_widget.select_callback(
                        focused_widget.name,
                        focused_widget.description,
                        focused_widget.cover_path,
                        focused_widget.appid,
                        focused_widget.controller_support,
                        focused_widget.exec_line,
                        focused_widget.last_launch,
                        focused_widget.formatted_playtime,
                        focused_widget.protondb_tier,
                        focused_widget.game_source
                    )
        parent = focused_widget.parent()
        while parent:
            if isinstance(parent, FileExplorer):
                parent.select_item()
                break
            parent = parent.parent()

    def goBackDetailPage(self, page: QWidget | None) -> None:
        if page is None or page != self.stackedWidget.currentWidget() or getattr(self, '_exit_animation_in_progress', False):
            return
        self._exit_animation_in_progress = True
        self._detail_page_active = False
        self._current_detail_page = None

        def cleanup():
            """Helper function to clean up after animation."""
            try:
                if page in self._animations:
                    animation = self._animations[page]
                    try:
                        if animation.state() == QAbstractAnimation.State.Running:
                            animation.stop()
                    except RuntimeError:
                        pass  # Animation already deleted
                    finally:
                        del self._animations[page]
                self.stackedWidget.setCurrentIndex(0)
                self.stackedWidget.removeWidget(page)
                page.deleteLater()
                self.currentDetailPage = None
                self.current_exec_line = None
                self.current_play_button = None
                self._exit_animation_in_progress = False
            except Exception as e:
                logger.error(f"Error in cleanup: {e}", exc_info=True)
                self._exit_animation_in_progress = False

        # Start exit animation
        try:
            self.detail_animations.animate_detail_page_exit(page, cleanup)
        except Exception as e:
            logger.error(f"Error starting exit animation: {e}", exc_info=True)
            self._exit_animation_in_progress = False
            cleanup()  # Fallback to cleanup if animation fails

    def is_target_exe_running(self):
        """Проверяет, запущен ли процесс с именем self.target_exe через psutil."""
        if not self.target_exe:
            return False
        for proc in psutil.process_iter(attrs=["name"]):
            if proc.info["name"].lower() == self.target_exe.lower():
                return True
        return False

    def checkTargetExe(self):
        """
        Проверяет, запущена ли игра.
        Если процесс игры (target_exe) обнаружен – устанавливаем флаг и обновляем кнопку.
        Если игра завершилась – сбрасываем флаг, обновляем кнопку и останавливаем таймер.
        """
        target_running = self.is_target_exe_running()
        child_running = any(proc.poll() is None for proc in self.game_processes)

        if target_running:
            # Игра стартовала – устанавливаем флаг, обновляем кнопку на "Stop"
            self._gameLaunched = True
            if self.current_running_button is not None:
                self.current_running_button.setText(_("Stop"))
                #self._inhibit_screensaver()
        elif not child_running:
            # Игра завершилась – сбрасываем флаг, сбрасываем кнопку и останавливаем таймер
            self._gameLaunched = False
            if hasattr(self, 'input_manager'):
                self.input_manager.enable_gamepad_handling()
            self.resetPlayButton()
            #self._uninhibit_screensaver()
            if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
                self.checkProcessTimer.stop()
                self.checkProcessTimer.deleteLater()
                self.checkProcessTimer = None

    def resetPlayButton(self):
        """
        Сбрасывает кнопку запуска игры:
        меняет текст на "Играть", устанавливает иконку и сбрасывает переменные.
        Вызывается, когда игра завершилась (не по нажатию кнопки).
        """
        if self.current_running_button is not None:
            self.current_running_button.setText(_("Play"))
            icon = self.theme_manager.get_icon("play")
            if isinstance(icon, str):
                icon = QIcon(icon)  # Convert path to QIcon
            elif icon is None:
                icon = QIcon()  # Use empty QIcon as fallback
            self.current_running_button.setIcon(icon)
            self.current_running_button = None
        self.target_exe = None

    def toggleGame(self, exec_line, button=None):
        # Обработка Steam-игр
        if exec_line.startswith("steam://"):
            url = QUrl(exec_line)
            QDesktopServices.openUrl(url)
            return

        # Обработка EGS-игр
        if exec_line.startswith("legendary:launch:"):
            app_name = exec_line.split("legendary:launch:")[1]

            # Получаем путь к .exe из installed.json
            game_exe = get_egs_executable(app_name, self.legendary_config_path)
            if not game_exe or not os.path.exists(game_exe):
                QMessageBox.warning(self, _("Error"), _("Executable not found for EGS game: {0}").format(app_name))
                return

            current_exe = os.path.basename(game_exe)
            if self.game_processes and self.target_exe is not None and self.target_exe != current_exe:
                QMessageBox.warning(self, _("Error"), _("Cannot launch game while another game is running"))
                return

            # Обновляем кнопку
            update_button = button if button is not None else self.current_play_button
            self.current_running_button = update_button
            self.target_exe = current_exe
            exe_name = os.path.splitext(current_exe)[0]

            # Проверяем, запущена ли игра
            if self.game_processes and self.target_exe == current_exe:
                # Останавливаем игру
                if hasattr(self, 'input_manager'):
                    self.input_manager.enable_gamepad_handling()

                for proc in self.game_processes:
                    try:
                        parent = psutil.Process(proc.pid)
                        children = parent.children(recursive=True)
                        for child in children:
                            try:
                                child.terminate()
                            except psutil.NoSuchProcess:
                                pass
                        psutil.wait_procs(children, timeout=5)
                        for child in children:
                            if child.is_running():
                                child.kill()
                        os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
                    except psutil.NoSuchProcess:
                        pass
                self.game_processes = []
                if update_button:
                    update_button.setText(_("Play"))
                    icon = self.theme_manager.get_icon("play")
                    if isinstance(icon, str):
                        icon = QIcon(icon)
                    elif icon is None:
                        icon = QIcon()
                    update_button.setIcon(icon)
                if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
                    self.checkProcessTimer.stop()
                    self.checkProcessTimer.deleteLater()
                    self.checkProcessTimer = None
                self.current_running_button = None
                self.target_exe = None
                self._gameLaunched = False
            else:
                # Запускаем игру через PortProton
                env_vars = os.environ.copy()
                env_vars['START_FROM_STEAM'] = '1'
                env_vars['LEGENDARY_CONFIG_PATH'] = self.legendary_config_path
                env_vars['PROCESS_LOG'] = '1'

                wrapper = "flatpak run ru.linux_gaming.PortProton"
                if self.portproton_location is not None and ".var" not in self.portproton_location:
                    start_sh = os.path.join(self.portproton_location, "data", "scripts", "start.sh")
                    wrapper = start_sh

                cmd = [wrapper, game_exe]

                try:
                    process = subprocess.Popen(cmd, env=env_vars, shell=False, preexec_fn=os.setsid)
                    self.game_processes.append(process)
                    save_last_launch(exe_name, datetime.now())
                    if update_button:
                        update_button.setText(_("Launching"))
                        icon = self.theme_manager.get_icon("stop")
                        if isinstance(icon, str):
                            icon = QIcon(icon)
                        elif icon is None:
                            icon = QIcon()
                        update_button.setIcon(icon)

                    # Delay disabling gamepad handling
                    if hasattr(self, 'input_manager'):
                        QTimer.singleShot(200, self.input_manager.disable_gamepad_handling)

                    self.checkProcessTimer = QTimer(self)
                    self.checkProcessTimer.timeout.connect(self.checkTargetExe)
                    self.checkProcessTimer.start(500)
                except Exception as e:
                    logger.error(f"Failed to launch EGS game {app_name}: {e}")
                    QMessageBox.warning(self, _("Error"), _("Failed to launch game: {0}").format(str(e)))
            return

        # Обработка PortProton-игр
        entry_exec_split = shlex.split(exec_line)
        if entry_exec_split[0] == "env":
            if len(entry_exec_split) < 3:
                QMessageBox.warning(self, _("Error"), _("Invalid command format (native)"))
                return
            file_to_check = entry_exec_split[2]
        elif entry_exec_split[0] == "flatpak":
            if len(entry_exec_split) < 4:
                QMessageBox.warning(self, _("Error"), _("Invalid command format (flatpak)"))
                return
            file_to_check = entry_exec_split[3]
        else:
            file_to_check = entry_exec_split[0]

        if not os.path.exists(file_to_check):
            QMessageBox.warning(self, _("Error"), _("File not found: {0}").format(file_to_check))
            return

        current_exe = os.path.basename(file_to_check)
        if self.game_processes and self.target_exe is not None and self.target_exe != current_exe:
            QMessageBox.warning(self, _("Error"), _("Cannot launch game while another game is running"))
            return

        # Обновляем кнопку
        update_button = button if button is not None else self.current_play_button

        # Если игра уже запущена для этого exe – останавливаем её
        if self.game_processes and self.target_exe == current_exe:
            if hasattr(self, 'input_manager'):
                self.input_manager.enable_gamepad_handling()

            for proc in self.game_processes:
                try:
                    parent = psutil.Process(proc.pid)
                    children = parent.children(recursive=True)
                    for child in children:
                        try:
                            child.terminate()
                        except psutil.NoSuchProcess:
                            pass
                    psutil.wait_procs(children, timeout=5)
                    for child in children:
                        if child.is_running():
                            child.kill()
                    os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
                except psutil.NoSuchProcess:
                    pass
            self.game_processes = []
            if update_button:
                update_button.setText(_("Play"))
                icon = self.theme_manager.get_icon("play")
                if isinstance(icon, str):
                    icon = QIcon(icon)
                elif icon is None:
                    icon = QIcon()
                update_button.setIcon(icon)
            if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
                self.checkProcessTimer.stop()
                self.checkProcessTimer.deleteLater()
                self.checkProcessTimer = None
            self.current_running_button = None
            self.target_exe = None
            self._gameLaunched = False
            #self._uninhibit_screensaver()
        else:
            # Сохраняем ссылку на кнопку для сброса после завершения игры
            self.current_running_button = update_button
            self.target_exe = current_exe
            exe_name = os.path.splitext(current_exe)[0]
            env_vars = os.environ.copy()

            if entry_exec_split[0] == "env" and len(entry_exec_split) > 1 and 'data/scripts/start.sh' in entry_exec_split[1]:
                env_vars['START_FROM_STEAM'] = '1'
                env_vars['PROCESS_LOG'] = '1'
            elif entry_exec_split[0] == "flatpak":
                env_vars['START_FROM_STEAM'] = '1'
                env_vars['PROCESS_LOG'] = '1'

            # Delay disabling gamepad handling to allow rumble to complete
            if hasattr(self, 'input_manager'):
                QTimer.singleShot(200, self.input_manager.disable_gamepad_handling)

            # Запускаем игру
            try:
                process = subprocess.Popen(entry_exec_split, env=env_vars, shell=False, preexec_fn=os.setsid)
                self.game_processes.append(process)
                save_last_launch(exe_name, datetime.now())
                if update_button:
                    update_button.setText(_("Launching"))
                    icon = self.theme_manager.get_icon("stop")
                    if isinstance(icon, str):
                        icon = QIcon(icon)
                    elif icon is None:
                        icon = QIcon()
                    update_button.setIcon(icon)

                self.checkProcessTimer = QTimer(self)
                self.checkProcessTimer.timeout.connect(self.checkTargetExe)
                self.checkProcessTimer.start(500)
            except Exception as e:
                logger.error(f"Failed to launch game {exe_name}: {e}")
                QMessageBox.warning(self, _("Error"), _("Failed to launch game: {0}").format(str(e)))


    def closeEvent(self, event):
        """Завершает все дочерние процессы и сохраняет настройки при закрытии окна."""
        for proc in self.game_processes:
            try:
                parent = psutil.Process(proc.pid)
                children = parent.children(recursive=True)
                for child in children:
                    try:
                        logger.debug(f"Terminating child process {child.pid}")
                        child.terminate()
                    except psutil.NoSuchProcess:
                        logger.debug(f"Child process {child.pid} already terminated")
                psutil.wait_procs(children, timeout=5)
                for child in children:
                    if child.is_running():
                        logger.debug(f"Killing child process {child.pid}")
                        child.kill()
                logger.debug(f"Terminating process group {proc.pid}")
                os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
            except (psutil.NoSuchProcess, ProcessLookupError) as e:
                logger.debug(f"Process {proc.pid} already terminated: {e}")

        self.game_processes = []  # Очищаем список процессов

        # Сохраняем настройки окна
        if not read_fullscreen_config():
            logger.debug(f"Saving window geometry: {self.width()}x{self.height()}")
            save_window_geometry(self.width(), self.height())
        save_card_size(self.card_width)

        # Очищаем таймеры и другие ресурсы
        if hasattr(self, 'games_load_timer') and self.games_load_timer.isActive():
            self.games_load_timer.stop()
        if hasattr(self, 'settingsDebounceTimer') and self.settingsDebounceTimer.isActive():
            self.settingsDebounceTimer.stop()
        if hasattr(self, 'searchDebounceTimer') and self.searchDebounceTimer.isActive():
            self.searchDebounceTimer.stop()
        if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer and self.checkProcessTimer.isActive():
            self.checkProcessTimer.stop()
            self.checkProcessTimer.deleteLater()
            self.checkProcessTimer = None

        QApplication.quit()
        event.accept()
