# -*- coding: utf-8 -*-
"""
trakt_module.py — Trakt.tv integrace pro plugin.video.mmirousek_v3
- TraktAPI: obálka nad Trakt API (GET/POST, token hlavičky, cache)
- FileCache: gzip JSON cache (namespace 'trakt')
- ProgressCacheManager: lokální JSON cache pokroku seriálů (cache-first + full refresh na vyžádání s progress barem)
- TraktMenu: renderery (Filmy/Seriály huby, Veřejné seznamy, Můj Trakt.tv, Watchlist, Kalendář,
  Pokrok sledování, Kolekce, Historie filmů/seriálů, People/Related/Next-Last)
- Prompt helpery: trakt_prompt_mark_season(tmdb_id, season=None), trakt_prompt_mark_episode(tmdb_id, season=None, episode=None)
- Utility: trakt_cache_invalidate_people(), trakt_cache_clear_all()
"""
import sys
import os
import json
import time
import gzip
import hashlib
from typing import Any, Dict, Optional, List
import requests
import xbmc
import xbmcvfs
import xbmcaddon
import xbmcgui
import xbmcplugin
from xbmcvfs import translatePath
from urllib.parse import urlencode
from datetime import datetime

from resources.lib.utils.ui_utils import pick_icon, make_static_item, add_menu_item, ensure_folder, prompt_and_filter_list, get_filter_param
from resources.lib.utils.log_utils import get_logger
from resources.lib.utils.context_builder import build_context_menu, style_context_menu
from resources.lib.database.metadata_db import db
from resources.lib.cache.menu_cache_server import get_menu_cache_server
from resources.lib.cache.menu_cache_config import (
    is_cache_enabled,
    get_cache_ttl,
    log_cache_event,
    kodi_cache_to_disc_for_menu,
)

log = get_logger()

# TMDb obrázky (poster/profile base URL) — používáme pro postery i profilové fotky
from resources.lib.tmdb.tmdb_module import IMAGE_BASE_URL_POSTER
# ==============================================================================
# --- IMPORT JÁDRA (TRAKT SERVICE) ---
# Všechny třídy a servisní funkce byly přesunuty do trakt_service.py
# ==============================================================================
# V trakt_module.py upravte import takto:
from resources.lib.trakt.trakt_service import (
    TraktAPI, 
    FileCache, 
    ProgressCacheManager, 
    WatchedMoviesCacheManager, 
    WatchedShowsCacheManager,
    trakt_progress_cache_refresh_full_ui,
    # NOVĚ PŘIDANÉ IMPORITY (protože jsme je přesunuli do service):
    get_tmdb_details_from_cache_only,
    _prefetch_tmdb_detail_async,
    _auth_headers, # Používá se v trakt_delete_history
    TRAKT_API_URL # Používá se v trakt_delete_history
)
ADDON = xbmcaddon.Addon('plugin.video.mmirousek_v3')
_addon = xbmcaddon.Addon('plugin.video.mmirousek_v3')
IMAGE_BASE_URL = 'https://image.tmdb.org/t/p/w500' 
FANART_BASE_URL = 'https://image.tmdb.org/t/p/original' 

_url = sys.argv[0]


def _current_plugin_folder_url() -> str:
    """Vrátí aktuální URL listu, který je právě renderován tímto router voláním."""
    try:
        query = sys.argv[2] if len(sys.argv) > 2 else ''
        query = str(query or '').strip()
        if query and not query.startswith('?'):
            query = '?' + query
        return f"{_url}{query}"
    except Exception:
        return xbmc.getInfoLabel('Container.FolderPath') or ''

# === KONFIGURACE TRAKT ===
TRAKT_CLIENT_SECRET = ADDON.getSetting('trakt_client_secret') or ""                    # musí být vyplněno v Settings

def get_url(**kwargs) -> str:
    """Bezpečné kódování parametrů do plugin URL."""
    return f'{_url}?{urlencode(kwargs, encoding="utf-8")}'


def _directory_items_for_plugin(directory_items):
    """addDirectoryItems vyžaduje n-tice (url, listitem, isFolder) — volitelný 4. prvek je context pro cache."""
    out = []
    for e in directory_items:
        if isinstance(e, tuple) and len(e) >= 3:
            out.append((e[0], e[1], e[2]))
        else:
            out.append(e)
    return out


def _end_directory_trakt_static_hub(handle, cache_key: str):
    """Kořenové Trakt huby — volitelně cacheToDisc (ARCHITECTURE bod 4)."""
    xbmcplugin.endOfDirectory(
        handle, cacheToDisc=kodi_cache_to_disc_for_menu(cache_key)
    )

# === IKONY PODMENU (resources/media/icons/trakt/*.png) =======================
def _icon(name: str) -> str:
    """
    Vrátí plnou cestu k ikoně v resources/media/icons/trakt/<name>.png.
    """
    try:
        base = ADDON.getAddonInfo('path')
        p = os.path.join(base, 'resources', 'media', 'icons', 'trakt', f'{name}.png')
        return p
    except Exception:
        return 'DefaultFolder.png'

# === Pomocné formátování CZ data =============================================

def _format_cz_date(iso_date: str) -> str:
    """
    Převede ISO datum ('YYYY-MM-DD' nebo 'YYYY-MM-DDTHH:MM:SSZ') na 'dd.mm.yyyy'.
    Vždy vrátí neprázdný řetězec; bezpečné pro různé varianty ISO.
    """
    try:
        d = (iso_date or '').strip()
        if not d:
            return '??.??.????'
        # Vezmeme jen datum část, zbytek zahodíme
        date_part = d.split('T')[0]
        # Rychlé rozparsování bez závislosti na locale
        y, m, day = date_part.split('-')
        return f"{int(day):02d}.{int(m):02d}.{int(y)}"
    except Exception:
        # Fallback: když nelze rozparsovat, vrať datum část nebo placeholder
        dp = (iso_date or '').split('T')[0].strip()
        if dp and '-' in dp:
            parts = dp.split('-')
            if len(parts) == 3:
                try:
                    y, m, day = parts
                    return f"{int(day):02d}.{int(m):02d}.{int(y)}"
                except Exception:
                    pass
        return '??.??.????'


def set_video_info(li, info_labels: dict):
    """Lokální setter metadata přes InfoTagVideo (bez importu z tmdb_kodi)."""
    if not isinstance(info_labels, dict):
        return
    try:
        tag = li.getVideoInfoTag()
    except Exception:
        return

    title = info_labels.get('title')
    originaltitle = info_labels.get('originaltitle')
    tvshowtitle = info_labels.get('tvshowtitle')
    plot = info_labels.get('plot')
    year = info_labels.get('year')
    mediatype = info_labels.get('mediatype')
    rating = info_labels.get('rating')
    votes = info_labels.get('votes')
    season = info_labels.get('season')
    episode = info_labels.get('episode')

    if title:
        tag.setTitle(str(title))
    if originaltitle:
        try:
            tag.setOriginalTitle(str(originaltitle))
        except Exception:
            pass
    if tvshowtitle:
        try:
            tag.setTvShowTitle(str(tvshowtitle))
        except Exception:
            pass
    if plot:
        tag.setPlot(str(plot))
    if isinstance(mediatype, str) and mediatype:
        try:
            tag.setMediaType(mediatype)
        except Exception:
            pass

    try:
        if isinstance(year, int):
            tag.setYear(year)
        elif isinstance(year, str) and year.isdigit():
            tag.setYear(int(year))
    except Exception:
        pass

    try:
        if rating is not None:
            tag.setRating(float(rating))
    except Exception:
        pass

    try:
        if votes is not None:
            tag.setVotes(int(votes))
    except Exception:
        pass

    try:
        if season is not None:
            tag.setSeason(int(season))
        if episode is not None:
            tag.setEpisode(int(episode))
    except Exception:
        pass


def refresh_trakt_token_by_expiry(threshold_hours: int = 12, notify_on_success: bool = True) -> None:
    """
    Obnoví OAuth access token, pokud zbývá méně než threshold_hours do vypršení
    (nebo už expiroval). Sdílené: router (maybe_refresh_on_start) a service.py.
    """
    log.info(f"[Trakt] refresh_trakt_token_by_expiry(threshold={threshold_hours}h, notify={notify_on_success})")

    try:
        issued_at_str = ADDON.getSetting('trakt_token_issued_at') or '0'
        expires_in_str = ADDON.getSetting('trakt_token_expires_in') or '86400'
        access_token = ADDON.getSetting('trakt_access_token') or ''
        refresh_token = ADDON.getSetting('trakt_refresh_token') or ''

        try:
            issued_at = int(issued_at_str)
        except (ValueError, TypeError):
            issued_at = 0
            log.warning(f"[Trakt] Chybný trakt_token_issued_at: '{issued_at_str}' → reset na 0")

        try:
            expires_in = int(expires_in_str)
        except (ValueError, TypeError):
            expires_in = 86400  # default 24 hodin
            log.warning(f"[Trakt] Chybný trakt_token_expires_in: '{expires_in_str}' → fallback na 86400")

        log.debug(f"[Trakt] expiry check: access={'yes' if access_token else 'no'}, "
                  f"refresh={'yes' if refresh_token else 'no'}, issued_at={issued_at}, expires_in={expires_in}s")

        if not (refresh_token and issued_at):
            log.debug("[Trakt] Nic k refreshi – chybí refresh_token nebo issued_at")
            return

        now = int(time.time())
        expires_at = issued_at + expires_in
        remaining_seconds = expires_at - now

        if remaining_seconds < (threshold_hours * 3600) or remaining_seconds <= 0:
            refreshed = TraktAPI().refresh_token_if_needed(force=False, notify_on_success=notify_on_success)
            if refreshed:
                log.info("[Trakt] Token obnoven (expiry check)")
            else:
                log.warning("[Trakt] Refresh při expiry check selhal nebo byl přeskočen")

            try:
                update_trakt_status_setting()
            except Exception:
                pass
        else:
            log.debug(f"[Trakt] Token ještě platný ({remaining_seconds}s do expirace), refresh neproběhl")

    except Exception as e:
        log.error(f"[Trakt] refresh_trakt_token_by_expiry neočekávaná chyba: {e}")


def maybe_refresh_on_start(threshold_hours: int = 12) -> None:
    log.info("[Trakt] maybe_refresh_on_start() spuštěna")
    refresh_trakt_token_by_expiry(threshold_hours=threshold_hours, notify_on_success=True)
        
def check_new_episodes_notifications(addon: xbmcaddon.Addon) -> None:
    """
    Zkontroluje Trakt kalendář uživatele na nově vydané epizody v uplynulých 2 dnech
    a zobrazí Kodi notifikaci, pokud najde něco nového.
    """
    try:
        from resources.lib.trakt.trakt_service import TraktAPI
        api = TraktAPI()
        
        # Ochrana - jsme na Traktu přihlášeni?
        if not addon.getSetting('trakt_access_token'):
            return

        import datetime
        import xbmcgui
        from resources.lib.utils.ui_utils import pick_icon
        
        # Zkontrolujeme včerejšek a dnešek (rozsah 2 dny)
        start_date = (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
        days = 2
        
        # Voláme Trakt API pro kalendář "mých" sledovaných seriálů
        data = api.get(f"calendars/my/shows/{start_date}/{days}", auth=True, cache_ttl=0) 
        
        if not data or not isinstance(data, list):
            return

        new_eps = []
        for item in data:
            show = item.get('show', {})
            ep = item.get('episode', {})
            show_title = show.get('title', 'Neznámý seriál')
            season = ep.get('season', 0)
            episode = ep.get('number', 0)
            new_eps.append(f"{show_title} S{season:02d}E{episode:02d}")

        if new_eps:
            unique_eps = list(dict.fromkeys(new_eps))
            message = ", ".join(unique_eps)
            
            # Oříznutí dlouhé zprávy
            if len(message) > 100:
                message = message[:97] + "..."
                
            xbmcgui.Dialog().notification(
                "Nová epizoda (Trakt)", 
                message, 
                pick_icon(addon, 'trakt.png', 'DefaultVideo.png'), 
                6000
            )
            log.info(f"[Trakt] Nové epizody notifikovány: {message}")

    except Exception as e:
        log.warning(f"[Trakt] Chyba při kontrole nových epizod: {e}")

# === Trakt Menu ==============================================================
class TraktMenu:
    def __init__(self, handle: int, addon: xbmcaddon.Addon) -> None:
        self.api = TraktAPI()
        self._handle = handle
        self._addon = addon

    # -- Helpers pro paging (UI) --
    def _read_pagination(self) -> Dict[str, int]:
        h = getattr(self.api, "last_headers", {}) or {}
        # Server/CDN může vracet hlavičky malými písmeny → lookup case-insensitive
        hl = {str(k).lower(): v for k, v in h.items()} if isinstance(h, dict) else {}
        def _to_int(v, default=0):
            try:
                return int(v)
            except Exception:
                return default
        return {
            "page": _to_int(hl.get("x-pagination-page"), 1),
            "page_count": _to_int(hl.get("x-pagination-page-count"), 1),
            "item_count": _to_int(hl.get("x-pagination-item-count"), 0),
            "limit": _to_int(hl.get("x-pagination-limit"), 0),
        }

    def _cache_load_directory_items(self, cache_key: str):
        if not is_cache_enabled(cache_key):
            return None

        cached = get_menu_cache_server().get_cached_menu(cache_key)
        if not cached:
            log_cache_event('MISS', cache_key)
            return None

        directory_items = []
        for item in cached.get('items', []):
            li = xbmcgui.ListItem(label=item.get('label', ''))
            info = item.get('info') or {}
            art = item.get('art') or {}
            props = item.get('properties') or {}
            cm = item.get('context') or []

            if info:
                set_video_info(li, info)
            if art:
                li.setArt(art)
            for k, v in props.items():
                li.setProperty(str(k), str(v))
            if cm:
                cm_norm = []
                for c in cm:
                    if isinstance(c, (list, tuple)) and len(c) >= 2:
                        cm_norm.append((str(c[0]), str(c[1])))
                if cm_norm:
                    cm_norm = style_context_menu(cm_norm)
                    li.addContextMenuItems(cm_norm, replaceItems=True)

            directory_items.append((item.get('url', ''), li, bool(item.get('isFolder', True))))

        log_cache_event('HIT', cache_key, f"{cached.get('count', len(directory_items))} items")
        return directory_items

    def _cache_save_directory_items(self, cache_key: str, directory_items: list) -> None:
        if not is_cache_enabled(cache_key):
            return

        serializable_items = []
        for entry in directory_items:
            if not isinstance(entry, tuple) or len(entry) < 3:
                continue
            url, li, is_folder = entry[0], entry[1], entry[2]
            cm = entry[3] if len(entry) >= 4 and isinstance(entry[3], list) else []
            art = {}
            if hasattr(li, 'getArt'):
                for key in ('icon', 'thumb', 'poster', 'fanart'):
                    try:
                        val = li.getArt(key)
                    except Exception:
                        val = ''
                    if val:
                        art[key] = val

            cm_json = []
            for c in cm:
                if isinstance(c, (list, tuple)) and len(c) >= 2:
                    cm_json.append([str(c[0]), str(c[1])])

            serializable_items.append((
                url,
                li.getLabel(),
                bool(is_folder),
                {'title': li.getLabel()},
                art,
                {},
                cm_json,
                False,
            ))

        get_menu_cache_server().cache_menu(cache_key, serializable_items, get_cache_ttl(cache_key))

    def _add_prev_page(self, action: str, **kwargs):
        directory_items = kwargs.pop('directory_items', None)
        try:
            current_page = int(kwargs.get('page', 1))
        except Exception:
            current_page = 1
        if current_page <= 1:
            return
        kwargs['page'] = current_page - 1
        li = xbmcgui.ListItem(label='<< Předchozí strana ...')
        li.setArt({'icon': _icon('trakt_lists_browse')})
        safe_kwargs = {k: str(v) for k, v in kwargs.items() if v not in (None, '', 0, '0') or k == 'page'}
        url = get_url(action=action, **safe_kwargs)
        
        if directory_items is None:
            xbmcplugin.addDirectoryItem(self._handle, url, li, isFolder=True)
        else:
            directory_items.append((url, li, True))

    def _add_next_page(self, action: str, only_if_more: bool = True, **kwargs):
        directory_items = kwargs.pop('directory_items', None)
        if only_if_more:
            pg = self._read_pagination()
            if pg.get("page") and pg.get("page_count") and pg["page"] >= pg["page_count"]:
                return
        try:
            current_page = int(kwargs.get('page', 1))
        except Exception:
            current_page = 1
        kwargs['page'] = current_page + 1
        li = xbmcgui.ListItem(label='>> Další strana ...')
        li.setArt({'icon': _icon('trakt_lists_browse')})
        safe_kwargs = {k: str(v) for k, v in kwargs.items() if v not in (None, '', 0, '0') or k == 'page'}
        url = get_url(action=action, **safe_kwargs)
        
        if directory_items is None:
            xbmcplugin.addDirectoryItem(self._handle, url, li, isFolder=True)
        else:
            directory_items.append((url, li, True))

    def search_trakt(self, query: str):
        """
        Vyhledá filmy a seriály na Trakt.tv, zobrazí detailní info přímo v seznamu.
        Kliknutí = Hledat na Webshare.
        Kontext menu = Obsazení, Podobné, Watchlist.
        """
        # Pokud get_url není viditelná, importujeme ji (pro jistotu)
        try:
            from .trakt_module import get_url
        except ImportError:
            pass # Předpokládáme, že je globální

        if not query:
            return

        xbmcplugin.setPluginCategory(self._handle, f"Trakt Vyhledávání: {query}")
        
        try:
            log.info(f"[TraktMenu] INFO: Volám Trakt API pro vyhledávání dotazu: {query}")
            
            # 1. Stažení dat z Trakt API
            data = self.api.get(
                "search/movie,show", 
                params={
                    'query': query,
                    'extended': 'full', # Klíčové pro získání roku, ratingu a overview
                },
                auth=False,
                cache_ttl=3600
            )

            if not data:
                xbmcgui.Dialog().notification('Trakt', f'Pro "{query}" nebyly nalezeny žádné výsledky.', xbmcgui.NOTIFICATION_WARNING)
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            list_items = []
            
            # Konstanty pro formátování (stejné jako v list_movies_by_mode)
            COLOR_HIGHLIGHT = '[COLOR FFFFFF00]' 
            COLOR_END = '[/COLOR]'
            COLOR_LABEL_RATING = '[COLOR FFFFFF00]'

            for item in data:
                item_type = item.get('type')
                media = item.get(item_type)
                
                if not media or item_type not in ('movie', 'show'):
                    continue

                # --- Extrakce základních dat ---
                ids = media.get('ids', {}) or {}
                tmdb_id = ids.get('tmdb')
                trakt_id = ids.get('trakt')
                slug = ids.get('slug')
                
                eng_title = media.get('title') or 'Bez názvu'
                year = media.get('year')
                rating = media.get('rating')
                votes = media.get('votes')
                genres_list = media.get('genres')
                certification = media.get('certification')
                overview = media.get('overview')

                # --- Načtení detailů z TMDb Cache (Poster + CZ název) ---
                poster_url = None
                plot_text = overview or ''
                cz_title = None
                
                # Zkusíme získat CZ data z cache, jinak spustíme async prefetch
                if tmdb_id:
                    # Rozlišení typu pro cache
                    cache_type = 'movie' if item_type == 'movie' else 'tv'
                    det = get_tmdb_details_from_cache_only(tmdb_id, cache_type)
                    
                    if det:
                        # Máme data v cache -> použijeme je
                        if det.get('title'): cz_title = det.get('title')
                        if det.get('plot'): plot_text = det.get('plot')
                        if det.get('poster_path'):
                            poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')
                    else:
                        # Nejsou v cache -> synchronní stažení
                        det = get_tmdb_details_fallback(tmdb_id, cache_type)
                        if det:
                            if det.get('plot'): plot_text = det.get('plot')
                            if det.get('poster_path'):
                                poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')

                # --- Formátování Popisu (Plot) ---
                genres_str = ', '.join(g.capitalize() for g in genres_list) if genres_list else 'Neznámé'
                rating_str = f"{rating:.2f}/10" if rating else 'N/A'
                votes_str = f"{votes:,.0f}".replace(',', ' ') if votes else '0'
                
                header_plot = (f"{COLOR_HIGHLIGHT}Žánry: {genres_str}\nHodnocení: {rating_str} ({votes_str} hlasů)\nCertifikace: {certification or 'N/A'}{COLOR_END}\n\n")
                final_plot = header_plot + (plot_text or 'Popis není k dispozici.')

                # --- Formátování Názvu (Label) ---
                rating_display = f"{COLOR_LABEL_RATING}{rating_str}{COLOR_END}"
                
                # Pokud máme CZ název a liší se od anglického, zobrazíme oba
                if cz_title and cz_title.strip() and cz_title != eng_title:
                    display_title = f"{cz_title} ({eng_title})"
                    info_title = cz_title
                else:
                    display_title = eng_title
                    info_title = eng_title

                base_label = f"{display_title} ({year}) {rating_display}" if year else f"{display_title} {rating_display}"

                # --- Vytvoření ListItem ---
                li = xbmcgui.ListItem(label=base_label)
                
                if poster_url:
                    li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
                else:
                    li.setArt({'icon': 'DefaultFolder.png'})

                set_video_info(li, {
                    'title': info_title, 
                    'originaltitle': eng_title,
                    'plot': final_plot, 
                    'year': year, 
                    'mediatype': 'movie' if item_type == 'movie' else 'tvshow',
                    'rating': rating,
                    'votes': votes
                })

                # --- Lazy Load do DB (pro Watchlist) ---
                if tmdb_id:
                    from resources.lib.database.metadata_db import db
                    metadata_to_save = {
                        'eng_title': eng_title,
                        'cz_title': cz_title,
                        'year': year,
                    }
                    db.get_or_create(tmdb_id, 'movie' if item_type == 'movie' else 'tv', metadata_to_save)

                # --- Kontextové menu ---
                contexts = ['trakt_base', 'trakt_watched', 'trakt_watchlist_add']
                
                item_data = {
                    'title': eng_title,
                    'tmdb_id': tmdb_id,
                    'trakt_id': trakt_id,
                    'slug': slug,
                    'media_type': 'movies' if item_type == 'movie' else 'shows'
                }
                context = build_context_menu(get_url, item_data, contexts)
                
                # Přidáme ručně položky specifické (seznam Trakt hledání)
                # Obsazení a Podobné už jsou pokryty sadou 'trakt_base'.
                if isinstance(context, list):
                    # ČSFD
                    csfd_url = get_url(
                        action='csfd_lookup_enhanced',
                        series_name=eng_title,
                        tmdb_id=tmdb_id or '',
                        media_type='movies' if item_type == 'movie' else 'shows',
                    )
                    context.append(("ČSFD detail (rozšířený)", f"RunPlugin({csfd_url})"))

                context = style_context_menu(context)
                li.addContextMenuItems(context, replaceItems=True)

                # --- Hlavní Akce po kliknutí ---
                if item_type == 'movie':
                    # PRO FILMY: Otevřít dialog pro vyhledání na Webshare
                    action_url = get_url(
                        action='show_search_dialog', 
                        cz_title=cz_title or '', 
                        eng_title=eng_title, 
                        year=year or '',
                        return_after_play_url=_current_plugin_folder_url(),
                    )
                elif item_type == 'show':
                    # PRO SERIÁLY: Otevřít seznam sérií (TMDb Seasons)
                    # Používáme tmdb_seasons, protože to je váš standard pro seriály
                    action_url = get_url(action='tmdb_seasons', tmdb_id=tmdb_id or '', title=display_title)
                
                list_items.append((action_url, li, True))
            
            # --- Vykreslení ---
            if list_items:
                xbmcplugin.addDirectoryItems(self._handle, list_items)
                xbmcplugin.setContent(self._handle, 'movies') # Nastavíme movies, aby skin zobrazil postery
                xbmc.executebuiltin('Container.SetViewMode(50)') # Vynutit seznam (volitelné)
            
        except Exception as e:
            log.error(f"[TraktMenu] CHYBA při vyhledávání na Trakt: {e}")
            xbmcgui.Dialog().notification(self._addon.getAddonInfo('name'), "Chyba při vyhledávání Trakt.tv", xbmcgui.NOTIFICATION_ERROR)

        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    def list_movies_by_mode(self, mode: str, page: int = 1, limit: int = 20,
                                genres: Optional[str] = None, years: Optional[str] = None):
            cache_key = f"trakt_movies_list_ctx2_{mode}_{page}_{limit}_{genres or ''}_{years or ''}"
            cached_items = self._cache_load_directory_items(cache_key)
            if cached_items is not None:
                xbmcplugin.setPluginCategory(self._handle, f"Trakt • Filmy • {mode}")
                xbmcplugin.addDirectoryItems(self._handle, _directory_items_for_plugin(cached_items))
                xbmcplugin.setContent(self._handle, 'movies')
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            # Krok 1: Přidání extended='full' pro získání detailních informací
            # Místo původního: data = self.api.movies(mode=mode, page=page, limit=limit, genres=genres, years=years)
            data = self.api.movies(
                mode=mode, page=page, limit=limit, 
                genres=genres, years=years, 
                extended='full' # <--- KLÍČOVÁ ZMĚNA ZDE!
            )
            pg   = self._read_pagination()
            hdr_present = (pg.get("limit", 0) > 0) or (pg.get("item_count", 0) > 0) or (pg.get("page_count", 1) > 1)
            srv_page   = pg.get("page", 1) if hdr_present else page
            page_count = max(pg.get("page_count", 1), srv_page)

            xbmcplugin.setPluginCategory(self._handle, f"Trakt • Filmy • {mode} • strana {srv_page}/{page_count}")
            if not data:
                xbmcgui.Dialog().notification('Trakt', 'Nic k zobrazení.', xbmcgui.NOTIFICATION_WARNING)
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            directory_items = []
            # Předchozí strana záměrně bez tlačítka — návrat řeší standardní ".." v Kodi

            for row in data:
                media = row.get('movie', row)
                if not isinstance(media, dict):
                    continue

                # Extrakce stávajících dat
                eng_title = media.get('title') or 'Bez názvu'
                year  = media.get('year')
                ids   = media.get('ids', {}) or {}
                imdb_id = ids.get('imdb')
                tmdb_id = ids.get('tmdb')
                trakt_id = ids.get('trakt')
                slug     = ids.get('slug')

                # Krok 2: Extrakce nových detailních dat z plného objektu 'media'
                rating = media.get('rating')
                votes = media.get('votes')
                genres_list = media.get('genres') # Vrací seznam, např. ['action', 'sci-fi']
                certification = media.get('certification')

                # === TMDb cache: pokus o načtení poster/plot ===
                poster_url = None
                plot_text  = ''
                cz_title = None
                if tmdb_id:
                    det = get_tmdb_details_fallback(tmdb_id, 'movie')
                    if det:
                        cz_title = det.get('title')
                        if det.get('poster_path'):
                            poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')
                        plot_text = det.get('plot') or ''
                
                # Krok 3: Vytvoření úvodního řádku pro Plot
                COLOR_HIGHLIGHT = '[COLOR FFFFFF00]' # Žlutá/Oranžová barva pro nadpis plotu
                COLOR_END = '[/COLOR]'

                # Sestavení řetězce žánrů
                genres_str = ', '.join(g.capitalize() for g in genres_list) if genres_list else 'Neznámé'
                
                # Formátování ratingu
                rating_str = f"{rating:.2f}/10" if rating else 'N/A'
                votes_str = f"{votes:,.0f}".replace(',', ' ') if votes else '0' # Přidání mezery pro tisíce
                
                # Sestavení prvního řádku (použijeme [COLOR] kód pro Kodi/XBMC)
                header_plot = (f"{COLOR_HIGHLIGHT}Žánry: {genres_str}\nHodnocení: {rating_str} ({votes_str} hlasů)\nCertifikace: {certification or 'N/A'}{COLOR_END}\n\n")
                # Spojení nového záhlaví s existujícím popisem
                final_plot = header_plot + (plot_text or 'Popis není k dispozici.')


                # Krok 4: Přidání ratingu do labelu (žlutou barvou)
                # Barva fiktivně: [COLOR FFFF00] (žlutá pro Kodi, může být potřeba upravit dle skinu/platformy)
                COLOR_LABEL_RATING = '[COLOR FFFFFF00]' # Použijeme stejnou oranžovou/žlutou

                rating_display = f"{COLOR_LABEL_RATING}{rating_str}{COLOR_END}"
                if cz_title and cz_title.strip() and cz_title != eng_title:
                    # Příklad: Český název (Anglický název)
                    display_title = f"{cz_title} ({eng_title})"
                    info_title = cz_title  # Pro li.setInfo použijeme CZ
                else:
                    # Jen anglický název
                    display_title = eng_title
                    info_title = eng_title

                base_label = f"{display_title} ({year}) ({rating_display})" if year else f"{display_title} ({rating_display})"
                if tmdb_id:
                    from resources.lib.database.metadata_db import db # Zajištění importu DB
                
                    # Sestavíme minimální metadata
                    metadata_to_save = {
                        'eng_title': eng_title,
                        'cz_title': cz_title, # Pokud máme z cache
                        'year': year,
                    }
                # Zajištění, že záznam existuje v lokální DB pro kontextové menu
                    db.get_or_create(tmdb_id, 'movie', metadata_to_save)
                
                # Původní: base_label = f"{title} ({year})" if year else title
                li = xbmcgui.ListItem(label=base_label)
                
                # artwork: pokud máme poster z cache, použij ho
                if poster_url:
                    li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
                else:
                    li.setArt({'icon': 'DefaultFolder.png'})
                
                # Nastavení upraveného Plotu
                set_video_info(li, {'title': info_title, 'plot': final_plot, 'year': year, 'mediatype': 'movie'})

                # ... zbytek kódu ...
                
                # Sjednocené context menu
                contexts = ['trakt_movies_default', 'trakt_watched', 'trakt_watchlist_add']
                
                item_data = {
                    'title': eng_title,
                    'tmdb_id': tmdb_id,
                    'trakt_id': trakt_id,
                    'slug': slug,
                    'media_type': 'movies'
                }
                context = build_context_menu(get_url, item_data, contexts)
                
                # --- PŘIDÁNÍ ČSFD POLOŽKY ---
                csfd_url = get_url(
                    action='csfd_lookup_enhanced',
                    series_name=eng_title,
                    tmdb_id=tmdb_id or '',
                    media_type='movies',
                )
                csfd_menu_item = ("ČSFD detail (rozšířený)", f"RunPlugin({csfd_url})")
                if isinstance(context, list):
                    context.append(csfd_menu_item)

                context = style_context_menu(context)
                li.addContextMenuItems(context, replaceItems=True)

                # Klik → vyhledání na Webshare

                dialog_url = get_url(
                    action='show_search_dialog',  # ← Nová akce
                    cz_title=cz_title or '',     # Český název (z cache)
                    eng_title=eng_title,             # Anglický název (z Traktu)
                    year=year or '',              # Rok
                    tmdb_id=str(tmdb_id), # 🚨 Přidat
                    mediatype='movie',  # 🚨 Přidat
                    return_after_play_url=_current_plugin_folder_url(),
                )
                
                # !! UPOZORNĚNÍ: Zde by mohla probíhat oprava, ale v search_trakt už se používá directory_items.
                # Abych nezlomil `addDirectoryItem` u kontextu: 
                directory_items.append((dialog_url, li, True, list(context)))

            # Další strana — číslo strany deterministicky z request parametru `page`
            has_more = (srv_page < page_count) if hdr_present else (isinstance(data, list) and limit and (len(data) >= limit))
            if has_more:
                self._add_next_page('trakt_movies_list', only_if_more=False, mode=mode, page=page, limit=limit,
                                    genres=genres or '', years=years or '', directory_items=directory_items)

            self._cache_save_directory_items(cache_key, directory_items)
            xbmcplugin.addDirectoryItems(self._handle, _directory_items_for_plugin(directory_items))

            xbmcplugin.setContent(self._handle, 'movies')
            xbmc.executebuiltin('Container.SetViewMode(50)')
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

# ----------------------------------------------------------------------

    def list_shows_by_mode(self, mode: str, page: int = 1, limit: int = 20,
                           genres: Optional[str] = None, years: Optional[str] = None):
        
        cache_key = f"trakt_shows_list_ctx2_{mode}_{page}_{limit}_{genres or ''}_{years or ''}"
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.setPluginCategory(self._handle, f"Trakt • Seriály • {mode}")
            xbmcplugin.addDirectoryItems(self._handle, _directory_items_for_plugin(cached_items))
            xbmcplugin.setContent(self._handle, 'tvshows')
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        # Krok 1: Volání s extended='full' pro získání ratingu, žánrů atd.
        data = self.api.shows(
            mode=mode, page=page, limit=limit, 
            genres=genres, years=years, 
            extended='full' # <--- KLÍČOVÁ ZMĚNA
        )
        pg   = self._read_pagination()
        hdr_present = (pg.get("limit", 0) > 0) or (pg.get("item_count", 0) > 0) or (pg.get("page_count", 1) > 1)
        srv_page   = pg.get("page", 1) if hdr_present else page
        page_count = max(pg.get("page_count", 1), srv_page)

        xbmcplugin.setPluginCategory(self._handle, f"Trakt • Seriály • {mode} • strana {srv_page}/{page_count}")
        if not data:
            xbmcgui.Dialog().notification('Trakt', 'Nic k zobrazení.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        directory_items = []
        # Předchozí strana záměrně bez tlačítka — návrat řeší standardní ".." v Kodi

        # Definuje spolehlivou barvu pro Kodi (Čistá žlutá/Oranžová)
        COLOR_HIGHLIGHT = '[COLOR FFFFFF00]' 
        COLOR_END = '[/COLOR]'
        COLOR_LABEL_RATING = '[COLOR FFFFFF00]'
        
        for row in data:
            media = row.get('show', row)
            if not isinstance(media, dict):
                continue

            # Extrakce stávajících dat (title je ENG název)
            ids      = media.get('ids', {}) or {}
            eng_title = media.get('title') or (ids.get('slug') or str(ids.get('trakt') or 'Bez názvu')) # Přejmenováno z 'title' na 'eng_title'
            year     = media.get('year')
            tmdb_id  = ids.get('tmdb')
            trakt_id = ids.get('trakt')
            slug     = ids.get('slug')
            
            # Krok 2: Extrakce detailních dat z plného objektu 'media'
            rating = media.get('rating')
            votes = media.get('votes')
            genres_list = media.get('genres')
            certification = media.get('certification')

            # === TMDb cache: pokus o načtení poster/plot + CZ titulu ===
            poster_url = None
            plot_text  = ''
            cz_title = None # 🚨 NOVÁ PROMĚNNÁ
            
            if tmdb_id:
                det = get_tmdb_details_fallback(tmdb_id, 'tv')
                if det:
                    # 🚨 Čteme CZ titul 
                    cz_title = det.get('title') 
                    
                    if det.get('poster_path'):
                        poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')
                    plot_text = det.get('plot') or ''
            # Krok 3: Formátování a přidání metadat do Plotu (Barevný header)
            
            genres_str = ', '.join(g.capitalize() for g in genres_list) if genres_list else 'Neznámé'
            rating_str = f"{rating:.2f}/10" if rating else 'N/A'
            votes_str = f"{votes:,.0f}".replace(',', ' ') if votes else '0'

            # Sestavení BAREVNÉHO záhlaví pro plot
            header_plot = (
                f"{COLOR_HIGHLIGHT}Žánry: {genres_str} \nHodnocení: {rating_str} ({votes_str} hlasů)\nCertifikace: {certification or 'N/A'}{COLOR_END}\n\n"
            )
            final_plot = header_plot + (plot_text or 'Popis není k dispozici.')


            # Krok 4: Přidání barevného ratingu do Labelu (sjednoceno s filmy)
            rating_display = f"{COLOR_LABEL_RATING}{rating_str}{COLOR_END}"
            
            # 🚨 NOVÁ LOGIKA SESTAVENÍ LABELU
            info_title = eng_title # Výchozí pro li.setInfo
            if cz_title and cz_title.strip() and cz_title != eng_title:
                # Příklad: Český název (Anglický název)
                display_title = f"{cz_title} ({eng_title})"
                info_title = cz_title # Pro li.setInfo použijeme CZ
            else:
                # Jen Anglický název
                display_title = eng_title
                info_title = eng_title # Pro li.setInfo použijeme ENG

            # Sestavení labelu: Název (Rok) [Barevný rating]
            base_label = f"{display_title} ({year}) ({rating_display})" if year else f"{display_title} ({rating_display})"
            
            li = xbmcgui.ListItem(label=base_label)
            
            if poster_url:
                li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
            else:
                li.setArt({'icon': 'DefaultFolder.png'})
                
            # Nastavení upraveného Plotu
            set_video_info(li, {'title': info_title, 'plot': final_plot, 'year': year, 'mediatype': 'tvshow'})

            contexts = ['trakt_base', 'trakt_watched', 'trakt_watchlist_add']
            
            item_data = {
                'title': eng_title, 
                'tmdb_id': tmdb_id,
                'trakt_id': trakt_id,
                'slug': slug,
                'media_type': 'shows'
            }
            context = build_context_menu(get_url, item_data, contexts)
            
            # 🚨 Uložíme metadata do lokální DB pro kontextové menu, pokud máme tmdb_id
            if tmdb_id:
                from resources.lib.database.metadata_db import db # Zajištění importu DB
                metadata_to_save = {
                    'eng_title': eng_title,
                    'cz_title': cz_title,
                    'year': year,
                }
                db.get_or_create(tmdb_id, 'tv', metadata_to_save)
            
            csfd_url = get_url(
                action='csfd_lookup_enhanced',
                series_name=eng_title,
                tmdb_id=tmdb_id or '',
                media_type='shows',
            )
            csfd_menu_item = ("ČSFD detail (rozšířený)", f"RunPlugin({csfd_url})")
            
            if isinstance(context, list):
                context.append(csfd_menu_item)

            context = style_context_menu(context)
            li.addContextMenuItems(context, replaceItems=True)

            url = get_url(
                action='tmdb_seasons', 
                tmdb_id=tmdb_id or '', 
                title=cz_title,
                fallback_title=eng_title or '', 
            ) 
            directory_items.append((url, li, True, list(context)))

        # Další strana — číslo strany deterministicky z request parametru `page`
        has_more = (srv_page < page_count) if hdr_present else (isinstance(data, list) and limit and (len(data) >= limit))
        if has_more:
            self._add_next_page('trakt_shows_list', only_if_more=False, mode=mode, page=page, limit=limit,
                                 genres=genres or '', years=years or '', directory_items=directory_items)

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, _directory_items_for_plugin(directory_items))

        xbmcplugin.setContent(self._handle, 'tvshows')
        xbmc.executebuiltin('Container.SetViewMode(50)')
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

        # -- Renderery položek --
    def _render_movie_item(self, media: Dict[str, Any], label_suffix: str = '', context_type: str = 'default'):
            """Vytvoří Kodi ListItem pro film."""
            
            # Extrahujeme detaily PŘÍMO z předaného dictionary 'media'
            title = media.get('title') or 'Bez názvu'
            year = media.get('year')
            plot = media.get('overview') or media.get('plot') or '' # plot je v TMDb jako 'overview'
            poster_partial = media.get('poster_path')
            
            # ID pro kontextové menu
            ids = media.get('ids') or {}
            tmdb_id = ids.get('tmdb') or media.get('id')
            trakt_id = ids.get('trakt')
            slug = ids.get('slug')
            
            # Nastavení labelu a prefixu
            if context_type == 'watchlist':
                prefix = "[COLOR magenta][Film][/COLOR] "
                base_label = f"{prefix}{title} ({year})" if year else f"{prefix}{title}"
            else:
                base_label = f"{title} ({year})" if year else title
                
            # Vytvoření ListItem s kompletním labelem
            li = xbmcgui.ListItem(label=(base_label + (label_suffix or '')))
            
            # Nastavení obrázků (poster/thumb)
            if poster_partial:
                # IMAGE_BASE_URL_POSTER je importován v hlavičce souboru
                poster_url = IMAGE_BASE_URL_POSTER + "/" + str(poster_partial).lstrip('/')
                li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
                
            # Nastavení informací o filmu (PLOT/YEAR/TITLE atd.)
            # Toto by mělo zajistit zobrazení detailů v Kodi
            set_video_info(li, {
                'title': title, 
                'plot': plot, 
                'year': year, 
                'mediatype': 'movie'
            })

            # --- Kontextové menu ---
            contexts = ['trakt_base', 'trakt_watched']
            if context_type == 'watchlist':
                contexts.append('trakt_watchlist_remove')
            else:
                contexts.append('trakt_watchlist_add')

            item_data = {
                'title': title,
                'tmdb_id': tmdb_id,
                'trakt_id': trakt_id,
                'slug': slug,
                'media_type': 'movies'
            }
            context = build_context_menu(get_url, item_data, contexts)
            li.addContextMenuItems(context, replaceItems=True)

            # Návrat tuple pro addDirectoryItems
            url = get_url(action='search', q=f"{title} {year}".strip())
            return (url, li, True)

    def _render_show_item(self, media: Dict[str, Any], label_suffix: str = '', context_type: str = 'progress'):
        ids = media.get('ids') or {}
        title = media.get('title') or (ids.get('slug') or str(ids.get('trakt') or 'Bez názvu'))
        year = media.get('year')
        tmdb_id = ids.get('tmdb')
        trakt_id = ids.get('trakt')
        slug = ids.get('slug')
        plot = media.get('overview') or ''

        poster_partial = None
        if tmdb_id:
            det = get_tmdb_details_fallback(tmdb_id, 'tv')
            if det:
                poster_partial = det.get('poster_path') or poster_partial
                title = det.get('title') or title
                plot = det.get('plot') or plot


        if context_type == 'watchlist':
            prefix = "[COLOR yellow][Seriál][/COLOR] "
            base_label = f"{prefix}{title} ({year})" if year else f"{prefix}{title}"
        else:
            base_label = f"{title} ({year})" if year else title

        li = xbmcgui.ListItem(label=(base_label + (label_suffix or '')))
        if poster_partial:
            poster_url = IMAGE_BASE_URL_POSTER + "/" + str(poster_partial).lstrip('/')
            li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
        set_video_info(li, {'title': title, 'plot': plot, 'year': year, 'mediatype': 'tvshow'})

        # Sjednocené kontext menu
        contexts = ['trakt_base', 'trakt_watched']
        if context_type == 'watchlist':
            contexts.append('trakt_watchlist_remove')
        else:
             contexts.append('trakt_watchlist_add')

        item_data = {
            'title': title,
            'tmdb_id': tmdb_id,
            'trakt_id': trakt_id,
            'slug': slug,
            'media_type': 'shows'
        }
        context = build_context_menu(get_url, item_data, contexts)

        # Zachovej speciální akci: zobrazit Next/Last epizodu
        if slug or trakt_id:
            next_last = get_url(action='trakt_show_next_last', id=(slug or str(trakt_id)), title=title)
            context.append(('Zobrazit Next/Last epizodu', f'Container.Update({next_last})'))

        context = style_context_menu(context)
        li.addContextMenuItems(context, replaceItems=True)

        url = get_url(action='tmdb_seasons', tmdb_id=tmdb_id or '', title=title)
        return (url, li, True)

    def trakt_lists_hub(self):
        """
        Hub pro veřejné seznamy Traktu (Popular / Trending).
        Odpovídá akci 'trakt_lists_hub' z routeru.
        """
        start_time = time.perf_counter() # PROFILER_START
        cache_key = 'trakt_lists_hub_menu'
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            log.info(f"TRAKT_PROFILER: trakt_lists_hub (VerejneSeznamy) trvalo {time.perf_counter() - start_time:.3f} s [CACHE HIT]")
            _end_directory_trakt_static_hub(self._handle, 'trakt_lists_hub_menu')
            return

        directory_items = []

        # Info řádek (neklikací)
        info_li = xbmcgui.ListItem(label='(Public) Veřejné seznamy lze procházet i bez přihlášení k Traktu.')
        info_li.setArt({'icon': 'DefaultInfo.png'})
        set_video_info(info_li, {
            'title': 'Informace • Veřejné seznamy',
            'plot': ('Procházení veřejných seznamů uživatelů Trakt. '
                    'Najdete zde tematické kolekce filmů a seriálů, které právě trendují '
                    'nebo patří dlouhodobě mezi nejpopulárnější.')
        })
        directory_items.append((get_url(action='noop'), info_li, False))

        # Popular lists
        li_pop = xbmcgui.ListItem(label='Seznamy • Popular')
        li_pop.setArt({'icon': 'DefaultFolder.png'})
        set_video_info(li_pop, {'title': 'Veřejné seznamy • Popular',
                    'plot': 'Nejčastěji sledované/odběry — dlouhodobě oblíbené seznamy.'})
        url_pop = get_url(action='trakt_lists_browse', mode='popular', page=1, limit=20)
        directory_items.append((url_pop, li_pop, True))

        # Trending lists
        li_tr = xbmcgui.ListItem(label='Seznamy • Trending')
        li_tr.setArt({'icon': 'DefaultFolder.png'})
        set_video_info(li_tr, {'title': 'Veřejné seznamy • Trending',
                       'plot': 'Aktuálně nejvíce vyhledávané a sdílené seznamy.'})
        url_tr = get_url(action='trakt_lists_browse', mode='trending', page=1, limit=20)
        directory_items.append((url_tr, li_tr, True))

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)

        log.info(f"TRAKT_PROFILER: trakt_lists_hub (VerejneSeznamy) trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        _end_directory_trakt_static_hub(self._handle, 'trakt_lists_hub_menu')


    # --- Hlavní menu Trakt (root) ---
    def show_main_menu(self):
        start_time = time.perf_counter() # PROFILER_START
        cache_key = 'trakt_main_menu'
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            log.info(f"TRAKT_PROFILER: show_main_menu (HlavniTraktMenu) trvalo {time.perf_counter() - start_time:.3f} s [CACHE HIT]")
            _end_directory_trakt_static_hub(self._handle, 'trakt_main_menu')
            return

        directory_items = []

        # Můj Trakt.tv (podmenu)
        li_my = xbmcgui.ListItem(label='Můj Trakt.tv')
        li_my.setArt({'icon': _icon('trakt_lists_hub')})
        set_video_info(li_my, {'title': 'Můj Trakt.tv',
                       'plot': 'Přehled vašeho Trakt účtu: Watchlist, Kalendář, pokrok, kolekce.'})
        directory_items.append((get_url(action='trakt_my_hub'), li_my, True))
        # --- NOVÁ VOLBA: Vyhledávání ---
        li_search = xbmcgui.ListItem(label='Hledej na Trakt.tv')
        # li_search.setArt({'icon': _icon('search_trakt') or 'DefaultAddSource.png'}) # Použijte vlastní ikonu, pokud existuje
        li_search.setArt({
            'icon': pick_icon(_addon, 'search.png', 'DefaultAddonsSearch.png'),
            'thumb': pick_icon(_addon, 'search.png', 'DefaultAddonsSearch.png') # Pro jistotu i thumb
        })
        set_video_info(li_search, {
            'title': 'Hledej na Trakt.tv',
            'plot': 'Vyhledá film nebo seriál na Trakt.tv podle názvu a zobrazí jeho detaily.'
        })
        # 🚨 Nová akce: trakt_search_dialog
        directory_items.append((get_url(action='trakt_search_dialog'), li_search, False))

        # Info (public)
        info_li = xbmcgui.ListItem(label='[COLOR red]Veřejné žebříčky lze procházet i bez přihlášení k Traktu.[/COLOR]')
        info_li.setArt({'icon': _icon('trakt_lists_hub')})
        directory_items.append((get_url(action='noop'), info_li, False))

        # Filmy hub
        li_movies = xbmcgui.ListItem(label='Filmy: Trending / Popular / Anticipated')
        li_movies.setArt({'icon': _icon('trakt_movies_hub')})
        directory_items.append((get_url(action='trakt_movies_hub'), li_movies, True))

        # Seriály hub
        li_shows = xbmcgui.ListItem(label='Seriály: Trending / Popular / Anticipated')
        li_shows.setArt({'icon': _icon('trakt_shows_hub')})
        directory_items.append((get_url(action='trakt_shows_hub'), li_shows, True))

        # Veřejné seznamy
        li_lists = xbmcgui.ListItem(label='Veřejné seznamy (Popular/Trending)')
        li_lists.setArt({'icon': _icon('trakt_lists_hub')})
        directory_items.append((get_url(action='trakt_lists_hub'), li_lists, True))

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)

        log.info(f"TRAKT_PROFILER: show_main_menu (HlavniTraktMenu) trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        _end_directory_trakt_static_hub(self._handle, 'trakt_main_menu')

    # --- Watchlist ---
    def show_trakt_list(self, list_type: str):
        start_time = time.perf_counter() # PROFILER_START
        endpoint = f'users/me/{list_type}'.lstrip('/')
        data = self.api.get(endpoint, params={'extended': 'full'}, auth=True, cache_ttl=0)
        if not data:
            xbmcgui.Dialog().notification('Trakt', 'Seznam je prázdný.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        directory_items = []
        for item in data:
            if 'movie' in item:
                res = self._render_movie_item(item['movie'], context_type='watchlist')
                if res: directory_items.append(res)
            elif 'show' in item:
                res = self._render_show_item(item['show'], context_type='watchlist')
                if res: directory_items.append(res)
        
        xbmcplugin.addDirectoryItems(self._handle, directory_items)

        xbmcplugin.setContent(self._handle, 'tvshows')
        xbmc.executebuiltin('Container.SetViewMode(50)')
        log.info(f"TRAKT_PROFILER: show_trakt_list trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    # --- Kalendář (my shows) ---
    def list_calendar_shows(self, days: int = 30, start_date: Optional[str] = None):
        start_time = time.perf_counter() # PROFILER_START
        if not ADDON.getSetting('trakt_access_token'):
            xbmcgui.Dialog().ok('Trakt', 'Nejste přihlášeni k Trakt.')
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return
        import datetime
        if not start_date:
            start_date = datetime.date.today().strftime('%Y-%m-%d')
        data = self.api.get(f'calendars/my/shows/{start_date}/{days}', auth=True, cache_ttl=0)
        if not data:
            xbmcgui.Dialog().ok('Trakt Kalendář', 'Žádné nadcházející epizody.')
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return
            
        directory_items = []
        for item in data:
            episode = item.get('episode', {})
            show = item.get('show', {})
            air = (item.get('first_aired') or '').split('T')[0]
            s, e = episode.get('season'), episode.get('number')
            s_txt = f"S{int(s):02d}" if isinstance(s, int) else "S??"
            e_txt = f"E{int(e):02d}" if isinstance(e, int) else "E??"
            show_title = show.get('title') or 'Neznámý seriál'
            ep_title = episode.get('title') or 'Epizoda'
            date_colored = f"[COLOR magenta]{_format_cz_date(air)}[/COLOR]"
            se_colored = f"[COLOR yellow]{s_txt}{e_txt}[/COLOR]"
            label = f"{date_colored} {se_colored} - {show_title} ({ep_title})"
            li = xbmcgui.ListItem(label=label)
            li.setArt({'icon': _icon('trakt_calendar')})
            tmdb_id = show.get('ids', {}).get('tmdb')
            plot = episode.get('overview') or show.get('overview') or 'Popis chybí.'
            det = get_tmdb_details_fallback(tmdb_id, 'tv') if tmdb_id else None
            if det and det.get('poster_path'):
                poster = IMAGE_BASE_URL_POSTER + "/" + det['poster_path'].lstrip('/')
                li.setArt({'icon': poster, 'thumb': poster, 'poster': poster})
            set_video_info(li, {
                'title': ep_title, 'tvshowtitle': show_title,
                'season': s or 0, 'episode': e or 0,
                'plot': plot, 'mediatype': 'episode'
            })
            url = get_url(action='series_search', what=show_title)
            directory_items.append((url, li, True))
        
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        log.info(f"TRAKT_PROFILER: list_calendar_shows trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    # --- Huby (Movies/Shows) ---
    def trakt_movies_hub(self):
        start_time = time.perf_counter() # PROFILER_START
        cache_key = 'trakt_movies_hub_menu'
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            log.info(f"TRAKT_PROFILER: trakt_movies_hub (FilmovyHub) trvalo {time.perf_counter() - start_time:.3f} s [CACHE HIT]")
            _end_directory_trakt_static_hub(self._handle, 'trakt_movies_hub_menu')
            return

        directory_items = []

        li = xbmcgui.ListItem(label='Trending (filmy)')
        li.setArt({'icon': _icon('trakt_movies_hub')})
        directory_items.append((get_url(action='trakt_movies_list', mode='trending', page=1, limit=20), li, True))

        li = xbmcgui.ListItem(label='Popular (filmy)')
        li.setArt({'icon': _icon('trakt_movies_hub')})
        directory_items.append((get_url(action='trakt_movies_list', mode='popular', page=1, limit=20), li, True))

        li = xbmcgui.ListItem(label='Anticipated (filmy)')
        li.setArt({'icon': _icon('trakt_movies_hub')})
        directory_items.append((get_url(action='trakt_movies_list', mode='anticipated', page=1, limit=20), li, True))

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)

        log.info(f"TRAKT_PROFILER: trakt_movies_hub (FilmovyHub) trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        _end_directory_trakt_static_hub(self._handle, 'trakt_movies_hub_menu')

    def trakt_shows_hub(self):
        start_time = time.perf_counter() # PROFILER_START
        cache_key = 'trakt_shows_hub_menu'
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            log.info(f"TRAKT_PROFILER: trakt_shows_hub (SerialovyHub) trvalo {time.perf_counter() - start_time:.3f} s [CACHE HIT]")
            _end_directory_trakt_static_hub(self._handle, 'trakt_shows_hub_menu')
            return

        directory_items = []

        li = xbmcgui.ListItem(label='Trending (seriály)')
        li.setArt({'icon': _icon('trakt_shows_hub')})
        directory_items.append((get_url(action='trakt_shows_list', mode='trending', page=1, limit=20), li, True))

        li = xbmcgui.ListItem(label='Popular (seriály)')
        li.setArt({'icon': _icon('trakt_shows_hub')})
        directory_items.append((get_url(action='trakt_shows_list', mode='popular', page=1, limit=20), li, True))

        li = xbmcgui.ListItem(label='Anticipated (seriály)')
        li.setArt({'icon': _icon('trakt_shows_hub')})
        directory_items.append((get_url(action='trakt_shows_list', mode='anticipated', page=1, limit=20), li, True))

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)

        log.info(f"TRAKT_PROFILER: trakt_shows_hub (SerialovyHub) trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        _end_directory_trakt_static_hub(self._handle, 'trakt_shows_hub_menu')

    # --- Public lists ---
    def list_public_lists(self, mode: str, page: int = 1, limit: int = 20):
        cache_key = f"trakt_public_lists_v2_{mode}_{page}_{limit}"
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.setPluginCategory(self._handle, f"Trakt • Seznamy • {mode}")
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        data = self.api.list_popular_or_trending(mode=mode, page=page, limit=limit)
        pg   = self._read_pagination()
        hdr_present = (pg.get("limit", 0) > 0) or (pg.get("page_count", 1) > 1)
        srv_page   = pg.get("page", 1) if hdr_present else page
        page_count = max(pg.get("page_count", 1), srv_page)

        xbmcplugin.setPluginCategory(self._handle, f"Trakt • Seznamy • {mode} • strana {srv_page}/{page_count}")
        if not data:
            xbmcgui.Dialog().notification('Trakt', 'Žádné seznamy.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        directory_items = []
        # Předchozí strana záměrně bez tlačítka — návrat řeší standardní ".." v Kodi

        for row in data:
            lst = row.get('list', row)
            if not isinstance(lst, dict):
                continue
            name = lst.get('name') or 'Seznam'
            ids  = lst.get('ids', {}) or {}
            # 🚨 OPRAVA: Pro globální endpointy (Popular/Trending) je číselné ID bezpečnější pro vyhnutí se chybě 400
            list_id = ids.get('trakt') or ids.get('slug')
            if not list_id:
                continue
            list_id = str(list_id)

            li = xbmcgui.ListItem(label=name)
            li.setArt({'icon': 'DefaultFolder.png'})
            url = get_url(action='trakt_list_items', list_id=list_id, page=1, limit=20)
            directory_items.append((url, li, True))

        # Další strana — číslo strany deterministicky z request parametru `page`
        has_more = (srv_page < page_count) if hdr_present else (isinstance(data, list) and limit and (len(data) >= limit))
        if has_more:
            self._add_next_page('trakt_lists_browse', only_if_more=False, mode=mode, page=page, limit=limit, directory_items=directory_items)

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    def list_items_of_list(self, list_id: str, page: int = 1, limit: int = 20):
        cache_key = f"trakt_list_items_v2_{list_id}_{page}_{limit}"
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.setPluginCategory(self._handle, f"Trakt • Položky seznamu • {list_id}")
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        # 🚨 OPRAVA: Pro veřejné seznamy nevyžadujeme autentizaci, aby se zobrazily i nepřihlášeným
        data = self.api.list_items(list_id=list_id, page=page, limit=limit, auth=False)
        pg   = self._read_pagination()
        hdr_present = (pg.get("limit", 0) > 0) or (pg.get("page_count", 1) > 1)
        srv_page   = pg.get("page", 1) if hdr_present else page
        page_count = max(pg.get("page_count", 1), srv_page)

        xbmcplugin.setPluginCategory(self._handle, f"Trakt • Položky seznamu • {list_id} • strana {srv_page}/{page_count}")
        if not data:
            xbmcgui.Dialog().notification('Trakt', 'Seznam je prázdný.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        directory_items = []
        # Předchozí strana záměrně bez tlačítka — návrat řeší standardní ".." v Kodi

        for item in data:
            # --- Filmy ---
            if 'movie' in item and isinstance(item['movie'], dict):
                media = item['movie']
                title = media.get('title') or 'Bez názvu'
                year  = media.get('year')
                ids   = media.get('ids', {}) or {}
                imdb_id = ids.get('imdb')
                tmdb_id = ids.get('tmdb')
                trakt_id = ids.get('trakt')
                slug     = ids.get('slug')

                poster_url = None
                plot_text  = ''
                if tmdb_id:
                    det = get_tmdb_details_fallback(tmdb_id, 'movie')
                    if det:
                        if det.get('poster_path'):
                            poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')
                        plot_text = det.get('plot') or ''
                li = xbmcgui.ListItem(label=f"{title} ({year})" if year else title)
                if poster_url:
                    li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
                else:
                    li.setArt({'icon': 'DefaultFolder.png'})
                set_video_info(li, {'title': title, 'plot': plot_text or '', 'year': year, 'mediatype': 'movie'})

                contexts = ['trakt_base', 'trakt_watched', 'trakt_watchlist_add']
                item_data = {
                    'title': title,
                    'tmdb_id': tmdb_id,
                    'trakt_id': trakt_id,
                    'slug': slug,
                    'media_type': 'movies'
                }
                cm = build_context_menu(get_url, item_data, contexts)
                li.addContextMenuItems(cm, replaceItems=True)

                url = get_url(action='search', q=f"{title} {year}".strip())
                directory_items.append((url, li, True))
                continue

            # --- Seriály ---
            if 'show' in item and isinstance(item['show'], dict):
                media = item['show']
                ids   = media.get('ids', {}) or {}
                title = media.get('title') or (ids.get('slug') or str(ids.get('trakt') or 'Bez názvu'))
                year  = media.get('year')
                tmdb_id  = ids.get('tmdb')
                trakt_id = ids.get('trakt')
                slug     = ids.get('slug')

                poster_url = None
                plot_text  = ''
                if tmdb_id:
                    det = get_tmdb_details_fallback(tmdb_id, 'tv')
                    if det:
                        if det.get('poster_path'):
                            poster_url = IMAGE_BASE_URL_POSTER + "/" + str(det['poster_path']).lstrip('/')
                        plot_text = det.get('plot') or ''
                li = xbmcgui.ListItem(label=f"{title} ({year})" if year else title)
                if poster_url:
                    li.setArt({'icon': poster_url, 'thumb': poster_url, 'poster': poster_url})
                else:
                    li.setArt({'icon': 'DefaultFolder.png'})
                set_video_info(li, {'title': title, 'plot': plot_text or '', 'year': year, 'mediatype': 'tvshow'})

                contexts = ['trakt_base', 'trakt_watched', 'trakt_watchlist_add']
                item_data = {
                    'title': title,
                    'tmdb_id': tmdb_id,
                    'trakt_id': trakt_id,
                    'slug': slug,
                    'media_type': 'shows'
                }
                cm = build_context_menu(get_url, item_data, contexts)
                li.addContextMenuItems(cm, replaceItems=True)

                url = get_url(action='tmdb_seasons', tmdb_id=tmdb_id or '', title=title)
                directory_items.append((url, li, True))
                continue

            # neznámá položka – přeskočit
            continue

        # Další strana — číslo strany deterministicky z request parametru `page`
        has_more = (srv_page < page_count) if hdr_present else (isinstance(data, list) and limit and len(data) >= limit)
        if has_more:
            self._add_next_page('trakt_list_items', only_if_more=False, list_id=list_id, page=page, limit=limit, directory_items=directory_items)

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
    # --- People / Related / Next-Last ---
    def show_people(self, media_type: str, trakt_id_or_slug: str, title: str):
        """
        People (obsazení a štáb) s preferencí relevantních oddělení:
        - nejdříve CAST (herci),
        - poté jen vybraná crew oddělení: directing (Režie), writing (Scénář),
          sound (Hudba), production (Produkce).
        Klik na osobu: Container.Update(plugin://.../?action=tmdb_person_detail&tmdb_id=...)
        (varianta B – spolehlivě přepne aktuální kontejner na 'detail osoby').
        """
        xbmcplugin.setPluginCategory(self._handle, f"Trakt • Obsazení a štáb • {title}")
        xbmcplugin.setContent(self._handle, 'files')
        log.debug(f"[People] media_type={media_type} id={trakt_id_or_slug} title={title}")

        # 1) zkus public people, pak auth fallback
        data = self.api.people(media_type=media_type, trakt_id_or_slug=trakt_id_or_slug, cache_ttl=0, auth=False)
        if not data:
            log.warning("[TraktMenu.show_people] public empty, retry auth=True")
            data = self.api.people(media_type=media_type, trakt_id_or_slug=trakt_id_or_slug, cache_ttl=0, auth=True)

        if not data or not isinstance(data, dict):
            xbmcgui.Dialog().notification('Trakt', 'Obsazení nedostupné.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        cast = data.get('cast') or []
        crew = data.get('crew') or {}

        # --- CAST (herci) ---
        MAX_CAST = 10
        for idx, p in enumerate(cast):
            if idx >= MAX_CAST:
                break
            person = p.get('person') or {}
            name = person.get('name') or 'Neznámé jméno'
            character = p.get('character') or ''
            tmdb_pid = (person.get('ids') or {}).get('tmdb')

            # Label: jméno — role (pokud je)
            label = f"{name} — {character}" if character else name
            li = xbmcgui.ListItem(label=label)
            li.setArt({'icon': _icon('trakt_people')})

            # TMDb fotka osoby (pokud je)
            thumb_url = get_tmdb_person_thumb(tmdb_pid) if tmdb_pid else None
            if thumb_url:
                li.setArt({'thumb': thumb_url, 'icon': thumb_url})

            # Klik → Container.Update na detail osoby
            if tmdb_pid:
                url = get_url(action='tmdb_person_detail', tmdb_id=int(tmdb_pid))
                log.debug(f"[People] cast person='{name}' tmdb_id={tmdb_pid} -> Container.Update")
                # Přidej do kontext menu možnost "Detail osoby" (necháváme i samotný click fungovat)
                li.addContextMenuItems(style_context_menu([('Detail osoby (TMDb)', f'Container.Update({url})')]), replaceItems=True)
                # Přidáváme položku jako "folder", aby click přirozeně vstoupil do dalšího listu.
                xbmcplugin.addDirectoryItem(self._handle, url, li, isFolder=True)
            else:
                log.debug(f"[People] cast person='{name}' tmdb_id=None")
                xbmcplugin.addDirectoryItem(self._handle, get_url(action='noop'), li, isFolder=False)

        # --- CREW (jen vybrané sekce) ---
        wanted_sections = {
            'directing': 'Režie',
            'writing': 'Scénář',
            'sound': 'Hudba',
            'production': 'Produkce',
        }
        for dept_key in ('directing', 'writing', 'sound', 'production'):
            members = crew.get(dept_key) or []
            if not members:
                continue

            header = xbmcgui.ListItem(label=f"{wanted_sections.get(dept_key, dept_key)} • {len(members)}")
            header.setArt({'icon': _icon('trakt_people')})
            xbmcplugin.addDirectoryItem(self._handle, get_url(action='noop'), header, isFolder=False)

            for m in members:
                person = m.get('person') or {}
                name = person.get('name') or 'Neznámé jméno'
                jobs = m.get('jobs') or m.get('job') or []  # list nebo string
                job_text = ''
                if isinstance(jobs, list) and jobs:
                    job_text = ', '.join([j for j in jobs if j])
                elif isinstance(jobs, str) and jobs.strip():
                    job_text = jobs.strip()

                label = f"{name}" + (f" — {job_text}" if job_text else "")
                li = xbmcgui.ListItem(label=label)
                li.setArt({'icon': _icon('trakt_people')})

                tmdb_pid = (person.get('ids') or {}).get('tmdb')
                thumb_url = get_tmdb_person_thumb(tmdb_pid) if tmdb_pid else None
                if thumb_url:
                    li.setArt({'thumb': thumb_url, 'icon': thumb_url})

                if tmdb_pid:
                    url = get_url(action='tmdb_person_detail', tmdb_id=int(tmdb_pid))
                    log.debug(f"[People] crew dept={dept_key} person='{name}' tmdb_id={tmdb_pid} -> Container.Update")
                    li.addContextMenuItems(style_context_menu([('Detail osoby (TMDb)', f'Container.Update({url})')]), replaceItems=True)
                    xbmcplugin.addDirectoryItem(self._handle, url, li, isFolder=True)
                else:
                    log.debug(f"[People] crew dept={dept_key} person='{name}' tmdb_id=None")
                    xbmcplugin.addDirectoryItem(self._handle, get_url(action='noop'), li, isFolder=False)

        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    def show_related(self, media_type: str, trakt_id_or_slug: str, page: int = 1, limit: int = 20):
        data = self.api.related(media_type=media_type, trakt_id_or_slug=trakt_id_or_slug, page=page, limit=limit)
        pg = self._read_pagination()
        hdr_present = (pg.get("limit", 0) > 0) or (pg.get("page_count", 1) > 1)
        srv_page = pg.get("page", 1) if hdr_present else page
        page_count = max(pg.get("page_count", 1), srv_page)
        xbmcplugin.setPluginCategory(self._handle, f"Trakt • Podobné ({media_type}) • strana {srv_page}/{page_count}")
        if not data:
            xbmcgui.Dialog().notification('Trakt', 'Žádné podobné položky.', xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return
        # Předchozí strana záměrně bez tlačítka — návrat řeší standardní ".." v Kodi
        directory_items = []
        for media in data:
            if media_type == 'movies':
                res = self._render_movie_item(media)
                if res: directory_items.append(res)
            else:
                res = self._render_show_item(media)
                if res: directory_items.append(res)
        # Další strana — číslo strany deterministicky z request parametru `page`
        has_more = (srv_page < page_count) if hdr_present else (isinstance(data, list) and limit and len(data) >= limit)
        if has_more:
            self._add_next_page('trakt_related', only_if_more=False, media_type=media_type, id=trakt_id_or_slug,
                                page=page, limit=limit, directory_items=directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    def show_next_last_episode(self, trakt_id_or_slug: str, show_title: str):
        next_ep = self.api.get(f'shows/{trakt_id_or_slug}/next_episode', params={'extended': 'full'},
                               auth=True, cache_ttl=0)
        last_ep = self.api.get(f'shows/{trakt_id_or_slug}/last_episode', params={'extended': 'full'},
                               auth=True, cache_ttl=0)
        def add_episode_li(ep, label_prefix):
            if not ep:
                return
            s = ep.get('season'); e = ep.get('number')
            s_txt = f"S{int(s):02d}" if isinstance(s, int) else "S??"
            e_txt = f"E{int(e):02d}" if isinstance(e, int) else "E??"
            ep_title = ep.get('title') or 'Epizoda'
            air_date_iso = (ep.get('first_aired') or '')
            air_date = _format_cz_date(air_date_iso) if air_date_iso else ''
            label = f"{label_prefix}: [{air_date}] {s_txt}{e_txt} - {show_title} ({ep_title})"
            li = xbmcgui.ListItem(label=label)
            li.setArt({'icon': _icon('trakt_next_last')})
            set_video_info(li, {'title': ep_title, 'tvshowtitle': show_title,
                                'season': s if isinstance(s, int) else 0,
                                'episode': e if isinstance(e, int) else 0,
                                'plot': ep.get('overview') or ''})
            xbmcplugin.addDirectoryItem(self._handle, get_url(action='noop'), li, isFolder=False)
        add_episode_li(next_ep, "Další epizoda")
        add_episode_li(last_ep, "Poslední epizoda")
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    # --- Můj Trakt.tv hub + Pokrok + Kolekce + Historie ---
    def show_my_hub(self):
        start_time = time.perf_counter() # PROFILER_START
        token = ADDON.getSetting('trakt_access_token')

        if not token:
            li = xbmcgui.ListItem(label='(Přihlásit se k Trakt)')
            li.setArt({'icon': _icon('trakt_lists_hub')})
            xbmcplugin.addDirectoryItem(self._handle, get_url(action='trakt_auth'), li, False)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        cache_key = 'trakt_my_hub_menu'
        cached_items = self._cache_load_directory_items(cache_key)
        if cached_items is not None:
            xbmcplugin.addDirectoryItems(self._handle, cached_items)
            log.info(f"TRAKT_PROFILER: show_my_hub (MujTraktHub) trvalo {time.perf_counter() - start_time:.3f} s [CACHE HIT]")
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        directory_items = []
        # Watchlist
        li_wl = xbmcgui.ListItem(label='Můj Watchlist (Filmy/Seriály)')
        li_wl.setArt({'icon': _icon('trakt_lists_hub')})
        directory_items.append((get_url(action='trakt_list', list_type='watchlist'), li_wl, True))

        # Kalendář
        li_cal = xbmcgui.ListItem(label='Kalendář (Nové epizody)')
        li_cal.setArt({'icon': _icon('trakt_calendar')})
        directory_items.append((get_url(action='trakt_calendar'), li_cal, True))

        # Pokrok (jen rozkoukané) — cache-first
        li_prog_incomplete = xbmcgui.ListItem(label='Pokrok sledování seriálů (jen rozkoukané)')
        li_prog_incomplete.setArt({'icon': _icon('trakt_shows_hub')})
        set_video_info(li_prog_incomplete, {'plot': 'Seznam pouze těch seriálů, které ještě nejsou dokoukané.'})
        directory_items.append((get_url(action='trakt_shows_progress', page=1, limit=20, mode='cache', only_incomplete='true'), li_prog_incomplete, True))

        # Pokrok (posledních 90 dnů)
        li_prog = xbmcgui.ListItem(label='Pokrok sledování seriálů (posledních 90 dnů)')
        li_prog.setArt({'icon': _icon('trakt_shows_hub')})
        set_video_info(li_prog, {'plot': 'Relevantní seriály podle vaší historie za posledních 90 dnů.'})
        directory_items.append((get_url(action='trakt_shows_progress', page=1, limit=20, mode='recent90'), li_prog, True))
        # Historie filmů
        li_watched_movies = xbmcgui.ListItem(label='Historie filmů (Watched Movies)')
        li_watched_movies.setArt({'icon': _icon('trakt_movies_hub')})
        set_video_info(li_watched_movies, {'plot': 'Seznam filmů, které byly zhlédnuty na Traktu.'})
        directory_items.append((get_url(action='trakt_watched_movies', page=1, limit=20), li_watched_movies, True))

        # Historie seriálů
        li_watched_shows = xbmcgui.ListItem(label='Historie seriálů (Watched Shows)')
        li_watched_shows.setArt({'icon': _icon('trakt_shows_hub')})
        set_video_info(li_watched_shows, {'plot': 'Seznam seriálů, které byly zhlédnuty na Traktu.'})
        directory_items.append((get_url(action='trakt_watched_shows', page=1, limit=20), li_watched_shows, True))
        li_refresh = xbmcgui.ListItem(label='Aktualizace cache Trakt (pokrok + historie)')
        li_refresh.setArt({'icon': _icon('trakt_lists_hub')})
        set_video_info(li_refresh, {'plot': 'Provede kompletní stažení pokroku z Trakt a uloží do lokální cache.'})
        directory_items.append((get_url(action='trakt_progress_cache_refresh_full_ui'), li_refresh, False))

        # --- VLOŽIT ZDE: Statistiky uživatele ---
        # neklikací statistiky (volitelné)
        def _fmt_minutes_to_days_hours(mins):
            try:
                m = int(mins) if mins else 0
            except Exception:
                m = 0
            days = m // (60 * 24)
            hours = (m // 60) % 24
            if days > 0:
                return f"{days}d {hours}h"
            if hours > 0:
                return f"{hours}h"
            return "0h"

        try:
            # 1. Zjistit username (pokud ho nemáme v settings, zkusíme fetch)
            # Pozn: Ideálně bys mohl username cachovat, ale GET users/me je rychlý
            user = self.api.get('users/me', auth=True, cache_ttl=3600) # cache na hodinu stačí
            username = (user or {}).get('username') if user else None
            
            stats = None
            if username:
                # 2. Načíst statistiky (cache 3 minuty)
                stats = self.api.get(f'users/{username}/stats', auth=True, cache_ttl=900)

            if stats:
                movies   = stats.get('movies', {}) or {}
                shows    = stats.get('shows', {}) or {}
                episodes = stats.get('episodes', {}) or {}

                movies_watched   = movies.get('watched', 0)
                movies_minutes   = movies.get('minutes', 0)
                shows_watched    = shows.get('watched', 0)
                episodes_minutes = episodes.get('minutes', 0)


                stats_label = (
                    f"[COLOR magenta]Filmy:[/COLOR] {int(movies_watched)} "
                    f"• [COLOR cyan]Čas:[/COLOR] {_fmt_minutes_to_days_hours(movies_minutes)} "
                    f"• [COLOR yellow]Seriály:[/COLOR] {int(shows_watched)} "
                    f"• [COLOR orange]Epizody čas:[/COLOR] {_fmt_minutes_to_days_hours(episodes_minutes)}"
                )

                li_stats = xbmcgui.ListItem(label=stats_label)
                li_stats.setArt({'icon': 'DefaultInfo.png'})
                set_video_info(li_stats, {'title': 'Trakt Statistiky', 'plot': 'Základní statistiky vašeho Trakt účtu.'})
                
                # isFolder=False a action='noop' zajistí, že to nic nedělá po kliknutí
                directory_items.append((get_url(action='noop'), li_stats, False))

        except Exception as e:
            log.warning(f"[Trakt] users stats fetch error: {e}")

        self._cache_save_directory_items(cache_key, directory_items)
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        log.info(f"TRAKT_PROFILER: show_my_hub (MujTraktHub) trvalo {time.perf_counter() - start_time:.3f} s") # PROFILER_END
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)


    # --- RYCHLÉ načítání pokroku ze CACHE + filtr jen rozkoukané + SORT last_activity DESC ---
    def list_shows_progress(self, page: int = 1, limit: int = 20, year: str = '',
                           mode: str = 'cache', start_at: str = '', end_at: str = '',
                           only_incomplete: str = ''):
        from resources.lib.database.metadata_db import db
        log.info(f"[MenuCache] list_shows_progress called page={page} limit={limit} year='{year}' only_incomplete='{only_incomplete}'")
        
        # === MENU CACHE: Try fetch from cache (cross-process) ===
        from resources.lib.cache.menu_cache_server import get_menu_cache_server
        from resources.lib.cache.menu_cache_config import is_cache_enabled, get_cache_ttl, log_cache_event
        
        cache_server = get_menu_cache_server()
        cache_key = f"trakt_shows_progress_{year}_{only_incomplete}_{page}_{limit}"
        
        if is_cache_enabled('trakt_shows_progress'):
            cached = cache_server.get_cached_menu(cache_key)
            if cached:
                log_cache_event('HIT', 'trakt_shows_progress', f"page {page}, {cached['count']} items")
                directory_items = []
                for item in cached.get('items', []):
                    # Defensive filter: older cached payloads may contain pagination rows.
                    label = str(item.get('label', ''))
                    if label.startswith('>> Další strana') or label.startswith('<< Předchozí strana'):
                        continue
                    li = xbmcgui.ListItem(label=item.get('label', ''))
                    art = item.get('art') or {}
                    info = item.get('info') or {}
                    props = item.get('properties') or {}
                    cm = item.get('context') or []
                    cm_norm = []
                    for c in cm:
                        if isinstance(c, (list, tuple)) and len(c) >= 2:
                            cm_norm.append((str(c[0]), str(c[1])))
                    if art:
                        li.setArt(art)
                    if info:
                        li.setInfo('video', info)
                    for k, v in props.items():
                        li.setProperty(str(k), str(v))
                    if cm_norm:
                        cm_norm = style_context_menu(cm_norm)
                        li.addContextMenuItems(cm_norm, replaceItems=True)
                    directory_items.append((item.get('url', ''), li, bool(item.get('isFolder', True))))

                # Rebuild pagination rows on HIT using lightweight COUNT(*) query.
                try:
                    sql_count = """
                        SELECT COUNT(*)
                        FROM media m
                        WHERE m.media_type = 'tv'
                          AND (m.trakt_next_episode_id IS NOT NULL OR m.trakt_full_data IS NOT NULL)
                    """
                    params_count = []
                    if year:
                        sql_count += " AND m.year = ?"
                        params_count.append(year)
                    if only_incomplete.lower() == 'true':
                        sql_count += " AND (m.trakt_is_fully_watched IS NULL OR m.trakt_is_fully_watched = 0)"

                    total_cached = db.conn.execute(sql_count, params_count).fetchone()[0]
                    has_next = (page * limit) < int(total_cached)

                    if has_next:
                        self._add_next_page(
                            'trakt_shows_progress',
                            only_if_more=False,
                            page=page,
                            limit=limit,
                            mode=mode,
                            start_at=start_at,
                            end_at=end_at,
                            only_incomplete=only_incomplete or '',
                            directory_items=directory_items
                        )
                    log.info(f"[MenuCache] HIT pagination total={total_cached} has_next={has_next}")
                except Exception as e:
                    log.warning(f"[MenuCache] HIT pagination rebuild failed: {e}")

                xbmcplugin.setPluginCategory(self._handle, f"Můj Trakt.tv • Pokrok • (z cache)")
                xbmcplugin.addDirectoryItems(self._handle, directory_items)
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return
            else:
                log_cache_event('MISS', 'trakt_shows_progress', f"page {page}")

        sql = """
            SELECT 
                m.tmdb_id,
                m.title_cs,
                m.title_en,
                m.title_original,
                m.year,
                m.poster_path,
                m.backdrop_path,
                m.trakt_is_fully_watched,
                m.trakt_next_episode_id,
                m.trakt_episodes_aired,
                m.trakt_episodes_completed,
                m.trakt_next_episode_title,
                m.trakt_total_episodes
            FROM media m
            WHERE m.media_type = 'tv'
              AND (m.trakt_next_episode_id IS NOT NULL OR m.trakt_full_data IS NOT NULL)
        """
        params = []

        if year:
            sql += " AND m.year = ?"
            params.append(year)

        if only_incomplete.lower() == 'true':
            sql += " AND (m.trakt_is_fully_watched IS NULL OR m.trakt_is_fully_watched = 0)"

        sql += " ORDER BY m.trakt_last_watched_at DESC NULLS LAST, m.title_cs COLLATE NOCASE"

        try:
            rows = db.conn.execute(sql, params).fetchall()
        except Exception as e:
            log.error(f"[Trakt] list_shows_progress error: {e}")
            xbmcgui.Dialog().notification('Trakt', 'Chyba při načítání', xbmcgui.NOTIFICATION_ERROR)
            xbmcplugin.endOfDirectory(self._handle)
            return

        total = len(rows)
        if total == 0:
            xbmcgui.Dialog().notification('Trakt', 'Žádné rozkoukané seriály.', xbmcgui.NOTIFICATION_INFO)
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
            return

        start_idx = max((page - 1), 0) * limit
        end_idx = start_idx + limit
        page_items = rows[start_idx:end_idx]
        total_pages = max(((total + limit - 1) // limit), 1)

        xbmcplugin.setPluginCategory(self._handle, f"Můj Trakt.tv • Pokrok • strana {page}/{total_pages}")
        
        directory_items = []
        serializable_items = []

        # Záměrně bez "Předchozí strana" - návrat řešíme přes systémové zpět/tečky,
        # protože explicitní prev položka zde vytvářela matoucí navigační smyčky.

        for row in page_items:
            tmdb_id = row[0]
            title_cs = row[1]
            title_en = row[2]
            title_original = row[3]
            year_val = row[4]
            poster_path = row[5]
            backdrop_path = row[6]
            is_fully_watched = row[7] or 0
            next_episode_id = row[8]
            aired = row[9] or 0
            completed = row[10] or 0
            next_episode_title = row[11]
            trakt_total_episodes = row[12]

            # Název – přesně jako máš v list_watched_shows()
            title = title_cs or title_en or title_original or "Neznámý seriál"

            # Ratio + barva
            ratio_color = 'orange' if is_fully_watched == 0 else 'lime'
            ratio_txt = f"[COLOR {ratio_color}]{completed}/{trakt_total_episodes}[/COLOR]"
            label = f"{title} ({year_val}) {ratio_txt}" if year_val else f"{title} {ratio_txt}"

            # Další epizoda – jen u nedosledovaných
            if is_fully_watched == 0 and next_episode_id:
                label += f" • [COLOR lightblue]Další:[/COLOR] {next_episode_title}"

            li = xbmcgui.ListItem(label=label)
            art = {'icon': _icon('trakt_shows_hub')}
            li.setArt(art)

            # Fallback poster + plot – PŘESNĚ JAKO V list_watched_shows()
            plot = f"Zhlédnuto {completed} z {trakt_total_episodes} epizod"
            if is_fully_watched:
                plot += " – DOKONČENO"

            if not poster_path and tmdb_id:
                det = get_tmdb_details_fallback(tmdb_id, 'tv')
                if det:
                    poster_path = det.get('poster_path')
                    plot = det.get('plot') or plot

            if poster_path:
                poster_url = IMAGE_BASE_URL_POSTER + "/" + str(poster_path).lstrip('/')
                art.update({'thumb': poster_url, 'poster': poster_url})
                li.setArt({'thumb': poster_url, 'poster': poster_url})

            if backdrop_path:
                fanart_url = FANART_BASE_URL + "/" + str(backdrop_path).lstrip('/')
                art.update({'fanart': fanart_url})
                li.setArt({'fanart': fanart_url})

            info_dict = {
                'title': title,
                'plot': plot,
                'year': year_val,
                'mediatype': 'tvshow',
                'playcount': 1 if is_fully_watched else 0
            }
            li.setInfo('video', info_dict)

            url = get_url(action='tmdb_seasons', tmdb_id=tmdb_id, title=title)
            contexts = ['trakt_base', 'trakt_watched'] # Watchlist sem asi nepatří, jde o progress
            item_data = {
                'title': title,
                'tmdb_id': tmdb_id,
                'media_type': 'shows'
            }
            cm = build_context_menu(get_url, item_data, contexts)
            li.addContextMenuItems(cm, replaceItems=True)

            directory_items.append((url, li, True))
            serializable_items.append((url, label, True, info_dict, art, {}, cm, False))

        # Stránkování - další stránka
        if (page * limit) < total:
            self._add_next_page('trakt_shows_progress', only_if_more=False, page=page, limit=limit,
                                mode=mode, start_at=start_at, end_at=end_at, only_incomplete=only_incomplete or '', directory_items=directory_items)

        # === MENU CACHE: Save to cache before rendering ===
        if is_cache_enabled('trakt_shows_progress'):
            # Store only content rows. Pagination is always rebuilt dynamically.
            cache_server.cache_menu(cache_key, serializable_items, get_cache_ttl('trakt_shows_progress'))
        
        xbmcplugin.addDirectoryItems(self._handle, directory_items)
        xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)

    # --- Historie: Filmy ---

    def list_watched_movies(self, page: int = 1, limit: int = 50):
            """Zobrazí historii zhlédnutých filmů z lokální cache. (Opraveno pro řazení a detaily)"""
            
            try:
                from resources.lib.database.metadata_db import db
            except ImportError:
                log.error("[TraktMovies] Nelze importovat metadata_db.db!")
                db = None
            
            xbmcplugin.setPluginCategory(self._handle, "Můj Trakt.tv • Historie filmů")
            
            raw_tmdb_ids = WatchedMoviesCacheManager().load_cache() 
            
            if not raw_tmdb_ids:
                xbmcgui.Dialog().notification('Trakt', 'Cache historie filmů je prázdná. Spusťte aktualizaci.', xbmcgui.NOTIFICATION_INFO)
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            cache_key = f"trakt_watched_movies_{page}_{limit}"
            cached_items = self._cache_load_directory_items(cache_key)
            if cached_items is not None:
                xbmcplugin.addDirectoryItems(self._handle, cached_items)
                xbmcplugin.setContent(self._handle, 'movies')
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            # 1. Získání dat (ID, Datum, Počet) pro řazení z metadata.db
            movies_for_sorting = []
            if db:
                for tmdb_id_str in raw_tmdb_ids:
                    try:
                        tmdb_id = int(tmdb_id_str)
                        db_record = db.get(tmdb_id)
                        
                        # Vytvoříme jednoduchý slovník pro řazení
                        movies_for_sorting.append({
                            'tmdb_id': tmdb_id,
                            # DEFAULTNÍ HODNOTA pro řazení: Staré datum, pokud chybí (aby spadlo na konec)
                            'last_watched_at': db_record.get('trakt_last_watched_at', '1900-01-01T00:00:00Z') if db_record else '1900-01-01T00:00:00Z',
                            'plays': 1 if db_record and db_record.get('trakt_is_fully_watched') else 0
                        })
                    except ValueError:
                        log.warning(f"[TraktMovies] Neplatné TMDb ID: {tmdb_id_str}")

            # 2. Řazení podle data zhlédnutí (ISO 8601 stringy se dají řadit jako text)
            # Nejdřív zhlédnuté (nejnovější datum) se zobrazí nahoře
            movies_for_sorting.sort(key=lambda x: x['last_watched_at'], reverse=True)

            # 3. Stránkování - pracujeme se seřazeným listem slovníků
            total = len(movies_for_sorting)
            start_idx = (page - 1) * limit
            end_idx = start_idx + limit
            page_items = movies_for_sorting[start_idx:end_idx] 
            total_pages = max(((total + limit - 1) // limit), 1)
            xbmcplugin.setPluginCategory(self._handle, f"Můj Trakt.tv • Historie filmů • strana {page}/{total_pages}")

            directory_items = []
            if page > 1:
                self._add_prev_page('trakt_watched_movies', page=page, limit=limit, directory_items=directory_items)

            # 4. Iterace, Načtení detailů a Vykreslení
            for item in page_items:
                tmdb_id = item['tmdb_id'] 
                plays = item.get('plays') 
                last_watched_iso = item.get('last_watched_at') 

                # Zkusíme získat aktuální záznam z centrální SQLite databáze (přidána optimalizace pro čtení bez sítě)
                diag_record = db.get(tmdb_id) if db else None
                
                try:
                    # Načtení detailů (Optimalizováno pro rychlost - preferujeme DB)
                    movie_data = {}
                    
                    # Pokusíme se načíst údaje z lokální DB, pokud obsahují název a poster, čímž ušetříme pomalý dotaz na síť
                    has_local_data = diag_record and (diag_record.get('title_cs') or diag_record.get('title_en')) and diag_record.get('poster_path')
                    
                    if has_local_data:
                        # Rychlé složení dat z databáze namísto API
                        movie_data = {
                            'title': diag_record.get('title_cs') or diag_record.get('title_en') or diag_record.get('title_original'),
                            'year': diag_record.get('year'),
                            'overview': diag_record.get('overview_cs') or diag_record.get('overview_en') or '',
                            'poster_path': diag_record.get('poster_path'),
                            'ids': {'tmdb': tmdb_id}
                        }
                    else:
                        # Pokud lokální data nestačí, provedeme fallback na TMDb z internetu
                        movie_data = get_tmdb_details_fallback(tmdb_id, 'movie')

                    if not isinstance(movie_data, dict) or not movie_data.get('title'):
                        log.warning(f"[TraktMovies] Přeskočeno chybějící/neplatné detaily pro TMDb ID: {tmdb_id}")
                        continue

                    # Původní logika pro suffix
                    last_date = ''
                    suffix_parts = []
                    
                    # Zobrazíme datum jen, pokud je platné (tj. ne defaultní '1900...')
                    if last_watched_iso and last_watched_iso != '1900-01-01T00:00:00Z':
                        last_date = _format_cz_date(last_watched_iso)
                        suffix_parts.append(last_date)
                    
                    if isinstance(plays, int) and plays > 0:
                        suffix_parts.append(f"{plays}×")
                        
                    suffix = f" [COLOR blue]{' • '.join(suffix_parts)}[/COLOR]" if suffix_parts else ""
                    
                    res = self._render_movie_item(movie_data, label_suffix=suffix)
                    if res: directory_items.append(res)
                    
                except Exception as e:
                    import traceback 
                    log.error(f"[TraktMovies] FATAL error při zpracování filmu {tmdb_id}: {traceback.format_exc()}")
                    continue
            
            # Navigace - další strana
            if (page * limit) < total:
                self._add_next_page('trakt_watched_movies', only_if_more=False, page=page, limit=limit, directory_items=directory_items)

            self._cache_save_directory_items(cache_key, directory_items)
            xbmcplugin.addDirectoryItems(self._handle, directory_items)

            xbmcplugin.setContent(self._handle, 'movies')
            xbmc.executebuiltin('Container.SetViewMode(50)')
            xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
        
            # --- Historie: Seriály ---

    def list_watched_shows(self, url: str, page: int = 1, limit: int = 20):
            """Zobrazí historii zhlédnutých seriálů z lokální cache s možností filtrování."""
            
            # Zajištění importu univerzální funkce pro parsování filtru
            from resources.lib.utils.ui_utils import get_filter_param 

            # 1. NAČTENÍ A PŘÍPRAVA DAT
            cache_manager = WatchedShowsCacheManager()
            raw_cache = cache_manager.load_cache()  # Původní, nefiltrovaný seznam
            
            if not raw_cache:
                xbmcgui.Dialog().notification('Trakt', 'Cache historie seriálů je prázdná. Spusťte aktualizaci.', xbmcgui.NOTIFICATION_INFO)
                xbmcplugin.endOfDirectory(self._handle, cacheToDisc=False)
                return

            # 2. PARSOVÁNÍ FILTRU A FILTROVÁNÍ
            title_filter = get_filter_param(url, 'title_filter')

            cache_key = f"trakt_watched_shows_{title_filter or ''}_{page}_{limit}"
            cached_items = self._cache_load_directory_items(cache_key)
            if cached_items is not None:
                # Vždy znovu sestavit tlačítko filtru (triviální, bez API)
                if title_filter:
                    li_f = xbmcgui.ListItem(f"[ Zrušit filtr (Aktivní: {title_filter}) ]")
                    url_f = get_url(action='list_watched_shows')
                else:
                    li_f = xbmcgui.ListItem(f"[ Filtrovat historii seriálů ]")
                    url_f = get_url(action='trakt_prompt_filter_watched_shows')
                xbmcplugin.addDirectoryItem(self._handle, url_f, li_f, False)
                xbmcplugin.addDirectoryItems(self._handle, cached_items)
                xbmcplugin.setContent(self._handle, 'tvshows')
                xbmcplugin.endOfDirectory(self._handle, succeeded=True, cacheToDisc=False)
                return True

            if title_filter:
                filtered_cache = []
                filter_lower = title_filter.lower()
                for item in raw_cache:
                    # Filtr aplikujeme na prioritní název (en, cs, original)
                    title = item.get('title_en') or item.get('title_cs') or item.get('title_original') or ''
                    if isinstance(title, str) and title.lower().startswith(filter_lower):
                        filtered_cache.append(item)
                
                display_cache = filtered_cache
                xbmcplugin.setPluginCategory(self._handle, f"Můj Trakt.tv • Historie seriálů (Filtr: {title_filter})")
            else:
                display_cache = raw_cache
                xbmcplugin.setPluginCategory(self._handle, "Můj Trakt.tv • Historie seriálů")
            
            # NOVÉ: Řazení podle last_watched_at DESC (naposledy sledované první)
            try:
                # DEBUG: Tiskni field values (opravené klíče)
                for idx, item in enumerate(display_cache[:5]): 
                    title_log = item.get('title_en') or item.get('title_cs') or item.get('title_original')
                    date_log = item.get('trakt_last_watched_at')
                    log.info(f"[DEBUG list_watched_shows] item[{idx}] title={title_log} trakt_last_watched_at={date_log}")
                # DEBUG: Po řazení
                log.info(f"[DEBUG list_watched_shows] po řazení: top 3:")
                for idx, item in enumerate(display_cache[:3]):
                    title_log = item.get('title_en') or item.get('title_cs') or item.get('title_original')
                    date_log = item.get('trakt_last_watched_at')
                    log.info(f"  [{idx}] {title_log} → {date_log}")
            except Exception as e:
                log.warning(f"[WatchedShows] sort error: {e}")

            # 3. PŘIDÁNÍ ODKAZU NA FILTROVÁNÍ/ZRUŠENÍ (vždy jako první položka)
            if title_filter:
                li_filter = xbmcgui.ListItem(f"[ Zrušit filtr (Aktivní: {title_filter}) ]")
                url_filter_action = get_url(action='list_watched_shows')
            else:
                li_filter = xbmcgui.ListItem(f"[ Filtrovat historii seriálů ]")
                url_filter_action = get_url(action='trakt_prompt_filter_watched_shows')
                
            xbmcplugin.addDirectoryItem(self._handle, url_filter_action, li_filter, False)

            # 4. PAGINACE (NA ZÁKLADĚ display_cache)
            total = len(display_cache)
            start_idx = (page - 1) * limit
            end_idx = start_idx + limit
            page_items = display_cache[start_idx:end_idx]
            total_pages = max(((total + limit - 1) // limit), 1)
            
            if not title_filter:
                xbmcplugin.setPluginCategory(self._handle, f"Můj Trakt.tv • Historie seriálů • strana {page}/{total_pages}")

            directory_items = []
            
            # 📢 PŘIDÁNÍ PŘEDCHOZÍ STRÁNKY
            if page > 1:
                self._add_prev_page(
                    action='list_watched_shows', 
                    page=page, 
                    limit=limit,
                    directory_items=directory_items
                )

            # 5. RENDER POLOŽEK
            items_rendered_count = 0 # Počítadlo vykreslených položek
            for item in page_items:
                # Extrakce ID a titulu přímo z DB záznamu
                ids = {'tmdb': item.get('tmdb_id'), 'trakt': item.get('trakt_id'), 'slug': item.get('trakt_slug')}
                title = item.get('title_en') or item.get('title_cs') or item.get('title_original') or ids.get('slug') or 'Bez názvu'
                tmdb_id = item.get('tmdb_id')
                plot = item.get('plot') or item.get('overview_en') or item.get('overview_cs') or ''
                
                # --- Vylepšená logika: Používáme primárně lokální DB, fallback jen při chybějícím posteru ---
                poster_partial = item.get('poster_path')
                
                # Zabráníme hromadným synchronním API callům kontrolou, zda už máme obrázek v DB
                if not poster_partial and tmdb_id:
                    det = get_tmdb_details_fallback(tmdb_id, 'tv')
                    if det:
                        poster_partial = det.get('poster_path') or poster_partial
                        title = det.get('title') or title 
                        plot = det.get('plot') or plot
                
                # PŘÍPRAVA SUFFIXU (Label2)
                plays = item.get('trakt_episodes_completed') 
                last_watched_iso = item.get('trakt_last_watched_at') or ''
                # Import _format_cz_date zde kvůli závislosti
                # from resources.lib.utils import _format_cz_date
                last_date = _format_cz_date(last_watched_iso) if last_watched_iso else ''
                
                suffix_parts = []
                
                if last_date:
                    suffix_parts.append(f'Naposledy: {last_date}')
                if isinstance(plays, int) and plays >= 0:
                    suffix_parts.append(f"{plays}× zhlédnuto")
                
                suffix = f" [COLOR blue]{' • '.join(suffix_parts)}[/COLOR]" if suffix_parts else ""
                
                # PŘÍPRAVA DAT PRO self._render_show_item
                render_data = item.copy()
                render_data['title'] = title
                render_data['ids'] = ids
                render_data['tmdb_id'] = tmdb_id
                render_data['plot'] = plot 
                
                # Přidáme i url posteru, který jsme našli buď v DB nebo TMDb fallbaku
                if poster_partial:
                    render_data['poster_path'] = poster_partial
                
                # 5b. RENDERUJEME (Jedno volání)
                res = self._render_show_item(render_data, label_suffix=suffix)
                if res:
                    directory_items.append(res)
                    items_rendered_count += 1 # Započítáme úspěšné vykreslení
            
            # 📢 PŘIDÁNÍ DALŠÍ STRÁNKY
            if (page * limit) < total:
                # Použijeme metodu pro přidání další stránky
                self._add_next_page(
                    action='list_watched_shows', 
                    only_if_more=False, 
                    page=page, 
                    limit=limit,
                    directory_items=directory_items
            )
            
            self._cache_save_directory_items(cache_key, directory_items)
            xbmcplugin.addDirectoryItems(self._handle, directory_items)

            # 6. UKONČENÍ
            # 📢 KLÍČOVÁ OPRAVA: Používáme self._handle a kontrolujeme počet položek
            
            # Nastavení content type je dobré
            xbmcplugin.setContent(self._handle, 'tvshows') 
            
            if items_rendered_count == 0:
                # Pokud nic nevykreslujeme, musíme to Kodi oznámit
                log.info(f"[DEBUG list_watched_shows] Seznam je prázdný, vracím neúspěch.")
                xbmcplugin.endOfDirectory(self._handle, succeeded=False)
                return True
                
            log.info(f"[DEBUG list_watched_shows] Úspěšně vykresleno {items_rendered_count} položek. Volám endOfDirectory.")
            xbmcplugin.endOfDirectory(self._handle, succeeded=True, cacheToDisc=False)
            return True

# === TMDb Fallback ===========================================================
def get_tmdb_details_fallback(tmdb_id: Optional[int], media_type: str) -> Optional[Dict[str, Any]]:
    """
    Vrátí základní info z TMDb pro film/seriál (poster_path, title/name, plot, year).
    Nepoužívá žádné Trakt hlavičky, pouze TMDb API key z nastavení doplňku.
    """
    tmdb_api_key = ADDON.getSetting('tmdb_api_key')
    language_code = 'cs-CZ'
    if not tmdb_id:
        return None
    if not tmdb_api_key:
        log.error("[TMDb Fallback] Missing TMDb API Key in settings!")
        return None
    media_type_str = 'movie' if media_type == 'movie' else 'tv'
    endpoint = f"https://api.themoviedb.org/3/{media_type_str}/{int(tmdb_id)}"
    params = {'api_key': tmdb_api_key, 'language': language_code}
    try:
        r = requests.get(endpoint, params=params, timeout=7)
        r.raise_for_status()
        data = r.json()
        return {
            'poster_path': data.get('poster_path'),
            'title': data.get('title') if media_type_str == 'movie' else data.get('name'),
            'plot': data.get('overview'),
            'year': (data.get('release_date') or data.get('first_air_date') or '')[:4],
            'number_of_episodes': data.get('number_of_episodes') if media_type_str == 'tv' else None
        }

    except Exception as e:
        log.error(f"TMDb fallback error: {e}")
        return None

# === TMDb Osoba: fotka + detail =============================================
def get_tmdb_person_thumb(tmdb_person_id: Optional[int]) -> Optional[str]:
    """Vrátí URL profilové fotky osoby (TMDb 'profile_path')."""
    try:
        api_key = ADDON.getSetting('tmdb_api_key')
        if not (tmdb_person_id and api_key):
            return None
        url = f"https://api.themoviedb.org/3/person/{int(tmdb_person_id)}"
        params = {'api_key': api_key}
        r = requests.get(url, params=params, timeout=7)
        r.raise_for_status()
        data = r.json()
        profile_path = data.get('profile_path')
        if profile_path:
            return IMAGE_BASE_URL_POSTER + "/" + str(profile_path).lstrip('/')
    except Exception as e:
        log.warning(f"[TMDb Person] thumb error: {e}")
    return None

def get_tmdb_person_detail(tmdb_person_id: Optional[int], language_code: str = 'cs-CZ') -> Optional[Dict[str, Any]]:
    """Vrátí detail osoby: jméno, biography (CZ, fallback EN), profile_path."""
    api_key = ADDON.getSetting('tmdb_api_key')
    if not (tmdb_person_id and api_key):
        log.warning("[TMDb Person] API key nebo ID chybí")
        return None
    url = f"https://api.themoviedb.org/3/person/{int(tmdb_person_id)}"
    try:
        # 1) CZ pokus
        r = requests.get(url, params={'api_key': api_key, 'language': language_code}, timeout=7)
        r.raise_for_status()
        data = r.json()
        bio_cz = (data.get('biography') or '').strip()
        if not bio_cz:
            # 2) Fallback EN
            r_en = requests.get(url, params={'api_key': api_key, 'language': 'en-US'}, timeout=7)
            r_en.raise_for_status()
            data_en = r_en.json()
            bio_en = (data_en.get('biography') or '').strip()
            data['biography'] = bio_en  # přepíšeme prázdné CZ bio EN verzí
        return {
            'name': data.get('name'),
            'biography': data.get('biography') or '',
            'profile_path': data.get('profile_path')
        }
    except Exception as e:
        log.error(f"[TMDb Person] detail error: {e}")
        return None

# === Detail osoby (TMDb) =====================================================

def get_tmdb_person_credits(tmdb_person_id: int, language_code: str = 'cs-CZ') -> list:
    """
    Vrátí combined_credits (cast) pro danou osobu z TMDb.
    Pokud CZ lokalizace neobsahuje názvy, ponechá je tak jak jsou; při chybě provede fallback na EN.
    """
    api_key = ADDON.getSetting('tmdb_api_key')
    if not (tmdb_person_id and api_key):
        log.warning("[TMDb Person] API key nebo ID chybí – credits skip")
        return []
    url = f"https://api.themoviedb.org/3/person/{int(tmdb_person_id)}/combined_credits"
    try:
        # 1) Pokus s CZ
        r = requests.get(url, params={'api_key': api_key, 'language': language_code}, timeout=8)
        r.raise_for_status()
        data = r.json() or {}
        cast = data.get('cast') or []
        # Pokud by nebylo nic (nebo by se zjevně rozbil dotaz), zkus EN fallback
        if not cast:
            r_en = requests.get(url, params={'api_key': api_key, 'language': 'en-US'}, timeout=8)
            r_en.raise_for_status()
            data_en = r_en.json() or {}
            cast = data_en.get('cast') or []
        return cast
    except Exception as e:
        log.error(f"[TMDb Person] combined_credits error: {e}")
        return []

def show_tmdb_person_detail(tmdb_id: int):
    """
    Zobrazí detail osoby (jméno, fotka, bio) a POD ním filmografii
    rozdělenou na dvě sekce:
      • Filmy (seřazeno dle roku, sekundárně popularita)
      • Seriály (seřazeno dle roku, sekundárně popularita)

    Klik:
      - Film  → trakt_search (Webshare vyhledávání podle názvu + roku)
      - Seriál → tmdb_seasons (seznam sezón daného seriálu)

    Poznámka: Čerpá z TMDb combined_credits (cast). Crew se zde nezobrazuje.
    """
    handle = int(sys.argv[1])
    xbmcplugin.setPluginCategory(handle, "TMDb • Osoba")

    # --- 1) Detail osoby (bio CZ/EN, fotka) ---
    info = get_tmdb_person_detail(int(tmdb_id), language_code='cs-CZ') or {}
    name = info.get('name') or 'Neznámé jméno'
    bio = (info.get('biography') or '').strip()
    profile_path = info.get('profile_path')

    li_info = xbmcgui.ListItem(label=name)
    art = {'icon': _icon('trakt_people')}
    if profile_path:
        thumb = IMAGE_BASE_URL_POSTER + "/" + str(profile_path).lstrip('/')
        art.update({'thumb': thumb, 'icon': thumb})
    li_info.setArt(art)
    li_info.setInfo('Video', {'title': name, 'plot': bio or 'Biografie není k dispozici.'})
    xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), li_info, isFolder=False)

    # --- 2) Filmografie (combined_credits) ---
    credits = get_tmdb_person_credits(int(tmdb_id), language_code='cs-CZ') or []

    # Helper – robustní extrakce roku ze stringu 'YYYY-MM-DD'
    def _year_from_str(s: Optional[str]) -> int:
        try:
            t = (s or '').strip()
            return int(t[:4]) if len(t) >= 4 else 0
        except Exception:
            return 0

    # Připravíme dvě sekce: Filmy / Seriály
    movies, shows = [], []
    for c in credits:
        media_type = (c.get('media_type') or '').lower()
        if media_type not in ('movie', 'tv'):
            continue  # nechceme jiné typy (video, person apod.)

        title = (c.get('title') if media_type == 'movie' else c.get('name')) or 'Bez názvu'
        date = c.get('release_date') if media_type == 'movie' else c.get('first_air_date')
        year = _year_from_str(date)
        popularity = 0.0
        try:
            popularity = float(c.get('popularity') or 0.0)
        except Exception:
            popularity = 0.0

        item = {
            'title': title,
            'year': year,                         # hlavní řazení
            'popularity': popularity,             # sekundární řazení (stejné jako dříve)
            'character': c.get('character') or '',# role u filmu/seriálu (pokud je)
            'poster_path': c.get('poster_path'),
            'tmdb_mid': c.get('id')               # TMDb ID titulu (film/seriál)
        }
        if media_type == 'movie':
            movies.append(item)
        else:
            shows.append(item)

    # --- 3) Řazení: rok DESC, sekundárně popularita DESC ---
    # (Pokud rok není k dispozici, bere se jako 0 a automaticky padá níž.)
    movies.sort(key=lambda x: (x['year'], x['popularity']), reverse=True)
    shows.sort(key=lambda x: (x['year'], x['popularity']), reverse=True)

    # Volitelný limit na sekci (zachovat přehlednost listu)
    MAX_PER_SECTION = 60
    if MAX_PER_SECTION > 0:
        movies = movies[:MAX_PER_SECTION]
        shows = shows[:MAX_PER_SECTION]

    # --- 4) Vykreslení sekce Filmy ---
    if movies:
        header = xbmcgui.ListItem(label=f"[Filmy] • {len(movies)}")
        xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), header, isFolder=False)

        for m in movies:
            # Label: Název (rok) + případná role
            base_label = f"{m['title']} ({m['year']})" if m['year'] else m['title']
            if m['character']:
                base_label += f" — {m['character']}"

            li = xbmcgui.ListItem(label=base_label)

            # Artwork
            if m['poster_path']:
                purl = IMAGE_BASE_URL_POSTER + "/" + str(m['poster_path']).lstrip('/')
                li.setArt({'thumb': purl, 'icon': purl, 'poster': purl})

            # SPRÁVNÉ hledání – používá název a rok FILMU, ne herce!
            query = m['title']
            if m['year']:
                query += f" {m['year']}"
            url = get_url(action='search', q=query.strip())

            xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
            
    # --- 5) Vykreslení sekce Seriály ---
    if shows:
        header = xbmcgui.ListItem(label=f"[Seriály] • {len(shows)}")
        xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), header, isFolder=False)

        for s in shows:
            base_label = f"{s['title']} ({s['year']})" if s['year'] else s['title']
            if s['character']:
                base_label += f" — {s['character']}"

            li = xbmcgui.ListItem(label=base_label)

            # Artwork
            if s['poster_path']:
                purl = IMAGE_BASE_URL_POSTER + "/" + str(s['poster_path']).lstrip('/')
                li.setArt({'thumb': purl, 'icon': purl, 'poster': purl})

            # Klik → TMDb sezóny (pokud máme tmdb_id seriálu)
            if s['tmdb_mid']:
                url = get_url(action='tmdb_seasons', tmdb_id=int(s['tmdb_mid']), title=s['title'])
                xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)
            else:
                xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), li, isFolder=False)

    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)

# === Prompt helpery (module-level pro router) ================================

def trakt_prompt_mark_season(tmdb_id: int, season=None) -> None:
    """
    Prompt pro označení celé SEZÓNY na Traktu:
    - vyžádá číslo sezóny (nebo použije season z URL/kontextu),
    - nabídne výběr data (NOW vs AIRED),
    - zavolá mark_season_watched(tmdb_id, season, watched_mode).
    """
    LOG = "mm.trakt_prompt"

    try:
        tmdb_id = int(tmdb_id or 0)
    except Exception:
        tmdb_id = 0

    if tmdb_id <= 0:
        xbmcgui.Dialog().notification('Trakt', 'Chybí TMDb ID seriálu.', xbmcgui.NOTIFICATION_WARNING, 2500)
        log.error(f"[{LOG}] tmdb_id invalid: {tmdb_id}")
        return

    season_num = None
    if season is not None:
        try:
            sn = int(season)
            if sn >= 0:
                season_num = sn
        except (ValueError, TypeError):
            pass

    if season_num is None:
        s_txt = xbmcgui.Dialog().input('Číslo sezóny', type=xbmcgui.INPUT_NUMERIC)
        if not s_txt or not s_txt.isdigit():
            xbmcgui.Dialog().notification('Trakt', 'Zadejte číslo sezóny (jen čísla).', xbmcgui.NOTIFICATION_WARNING, 2500)
            log.warning(f"[{LOG}] season input invalid: {s_txt}")
            return
        season_num = int(s_txt)
        if season_num <= 0:
            xbmcgui.Dialog().notification('Trakt', 'Neplatné číslo sezóny.', xbmcgui.NOTIFICATION_WARNING, 2500)
            log.warning(f"[{LOG}] season <= 0")
            return

    # 2) výběr data
    choice = xbmcgui.Dialog().select(
        f"Zapsat zhlédnutí sezóny S{season_num:02d}",
        ["Použít aktuální datum", "Použít datum vysílání (premiéra epizod)"]
    )
    if choice < 0:
        log.info(f"[{LOG}] user canceled date prompt")
        return
    watched_mode = 'now' if choice == 0 else 'aired'
    log.info(f"[{LOG}] SEASON tmdb={tmdb_id} S={season_num} mode={watched_mode}")

    # 3) zápis na Trakt
    try:
        from resources.lib.trakt.trakt_bulk import mark_season_watched
        mark_season_watched(tmdb_id, season_num, watched_mode=watched_mode)
        xbmcgui.Dialog().notification('Trakt', 'Sezóna zapsána.', xbmcgui.NOTIFICATION_INFO, 2000)
    except Exception as e:
        log.error(f"[{LOG}] mark_season_watched error: {e}")
        xbmcgui.Dialog().notification('Trakt', 'Chyba při odeslání sezóny.', xbmcgui.NOTIFICATION_ERROR, 3000)


def trakt_prompt_mark_episode(tmdb_id: int, season=None, episode=None) -> None:
    """
    Prompt pro označení EPIZODY na Traktu:
    - vyžádá S/E (nebo použije season/episode z URL/kontextu),
    - nabídne výběr data (NOW vs AIRED),
    - zavolá mark_episode_watched(tmdb_id, season, episode, watched_mode).
    """
    LOG = "mm.trakt_prompt"

    try:
        tmdb_id = int(tmdb_id or 0)
    except Exception:
        tmdb_id = 0

    if tmdb_id <= 0:
        xbmcgui.Dialog().notification('Trakt', 'Chybí TMDb ID seriálu.', xbmcgui.NOTIFICATION_WARNING, 2500)
        log.error(f"[{LOG}] tmdb_id invalid: {tmdb_id}")
        return

    season_num = None
    if season is not None:
        try:
            sn = int(season)
            if sn >= 0:
                season_num = sn
        except (ValueError, TypeError):
            pass

    if season_num is None:
        s_txt = xbmcgui.Dialog().input('Číslo sezóny', type=xbmcgui.INPUT_NUMERIC)
        if not s_txt or not s_txt.isdigit():
            xbmcgui.Dialog().notification('Trakt', 'Zadejte číslo sezóny (jen čísla).', xbmcgui.NOTIFICATION_WARNING, 2500)
            log.warning(f"[{LOG}] season input invalid: {s_txt}")
            return
        season_num = int(s_txt)
        if season_num <= 0:
            xbmcgui.Dialog().notification('Trakt', 'Neplatné číslo sezóny.', xbmcgui.NOTIFICATION_WARNING, 2500)
            log.warning(f"[{LOG}] season <= 0 (manual)")
            return

    episode_num = None
    if episode is not None:
        try:
            en = int(episode)
            if en >= 1:
                episode_num = en
        except (ValueError, TypeError):
            pass

    if episode_num is None:
        e_txt = xbmcgui.Dialog().input('Číslo epizody', type=xbmcgui.INPUT_NUMERIC)
        if not e_txt or not e_txt.isdigit():
            xbmcgui.Dialog().notification('Trakt', 'Zadejte číslo epizody (jen čísla).', xbmcgui.NOTIFICATION_WARNING, 2500)
            log.warning(f"[{LOG}] episode input invalid: {e_txt}")
            return
        episode_num = int(e_txt)

    if episode_num <= 0:
        xbmcgui.Dialog().notification('Trakt', 'Neplatné číslo epizody.', xbmcgui.NOTIFICATION_WARNING, 2500)
        log.warning(f"[{LOG}] invalid episode: E={episode_num}")
        return

    # 2) výběr data
    choice = xbmcgui.Dialog().select(
        f"Zapsat zhlédnutí S{season_num:02d}E{episode_num:02d}",
        ["Použít aktuální datum", "Použít datum vysílání (premiéra epizody)"]
    )
    if choice < 0:
        log.info(f"[{LOG}] user canceled date prompt")
        return
    watched_mode = 'now' if choice == 0 else 'aired'
    log.info(f"[{LOG}] EP tmdb={tmdb_id} S={season_num} E={episode_num} mode={watched_mode}")

    # 3) zápis na Trakt
    try:
        from resources.lib.trakt.trakt_bulk import mark_episode_watched
        mark_episode_watched(tmdb_id, season_num, episode_num, watched_mode=watched_mode)
        xbmcgui.Dialog().notification('Trakt', 'Epizoda zapsána.', xbmcgui.NOTIFICATION_INFO, 2000)
    except Exception as e:
        log.error(f"[{LOG}] mark_episode_watched error: {e}")
        xbmcgui.Dialog().notification('Trakt', 'Chyba při odeslání epizody.', xbmcgui.NOTIFICATION_ERROR, 3000)

# === Odstranění historie (remove) ============================================
def trakt_delete_history(kind: str, trakt_id: int, season: Optional[int] = None) -> None:
    """
    Odstranění záznamů z historie Traktu.
    Endpoint: POST /sync/history/remove
    Chování:
    - Na Trakt odešle remove payload (celý seriál nebo konkrétní sezónu).
    - Po úspěchu (200 OK) lokálně odstraní daný seriál z progress_cache.json (drop_show_by_trakt_id).
    """
    headers = _auth_headers(for_post=True)  # Authorization + Content-Type
    if not headers:
        xbmcgui.Dialog().notification('Trakt', 'Nejste přihlášeni.', xbmcgui.NOTIFICATION_ERROR)
        return
    if not trakt_id:
        xbmcgui.Dialog().notification('Trakt', 'Chybí trakt_id.', xbmcgui.NOTIFICATION_WARNING)
        return
    kind = (kind or '').lower()
    if kind == 'season':
        if season is None or not isinstance(season, int) or season <= 0:
            xbmcgui.Dialog().notification('Trakt', 'Neplatná sezóna.', xbmcgui.NOTIFICATION_WARNING)
            return
        payload = {
            "shows": [
                {
                    "ids": {"trakt": int(trakt_id)},
                    "seasons": [{"number": int(season)}]
                }
            ]
        }
    else:
        payload = {"shows": [{"ids": {"trakt": int(trakt_id)}}]}
    url = f"{TRAKT_API_URL}/sync/history/remove"
    try:
        r = requests.post(url, headers=headers, json=payload, timeout=12)
        log.info(f"[TraktDelete] url={url} kind={kind} status={getattr(r,'status_code',None)} body={payload} "
                 f"resp={getattr(r, 'text', '')[:200]}")
        if r.status_code == 200:
            try:
                pcm = ProgressCacheManager()
                pcm.drop_show_by_trakt_id(trakt_id)
            except Exception as e:
                log.warning(f"[TraktDelete] local cache drop error: {e}")
            msg = 'Progress seriálu smazán.' if kind == 'show' else f'Sezóna {season} smazána.'
            xbmcgui.Dialog().notification('Trakt', msg, xbmcgui.NOTIFICATION_INFO)
        elif r.status_code == 401:
            xbmcgui.Dialog().notification('Trakt', 'Neplatný token (401) – přihlaste se znovu.', xbmcgui.NOTIFICATION_ERROR)
        else:
            xbmcgui.Dialog().notification('Trakt', f"Chyba {r.status_code}: {getattr(r, 'text', '')[:120]}",
                                          xbmcgui.NOTIFICATION_ERROR)
    except Exception as e:
        log.error(f"[TraktDelete] error: {e}")
        xbmcgui.Dialog().notification('Trakt', 'Chyba při mazání progressu.', xbmcgui.NOTIFICATION_ERROR)

# === Servisní utility: stav v Settings =======================================
def update_trakt_status_setting() -> None:
    """
    Zapíše do settings pole 'trakt_status' informaci o stavu přihlášení.
    Pokud je token, pokusí se načíst /users/me a ukáže username; jinak 'Nepřihlášen'.
    """
    try:
        addon = xbmcaddon.Addon('plugin.video.mmirousek_v3')
        token = addon.getSetting('trakt_access_token') or ''
        if not token:
            addon.setSetting('trakt_status', 'Nepřihlášen')
            return
        api = TraktAPI()
        me = api.get('users/me', params=None, auth=True, cache_ttl=0) or {}
        username = me.get('username') or (me.get('name') or '')
        if username:
            addon.setSetting('trakt_status', f'Přihlášen: {username}')
        else:
            addon.setSetting('trakt_status', 'Přihlášen (uživatel neidentifikován)')
    except Exception as e:
        log.warning(f"[Trakt] update_trakt_status_setting error: {e}")

