
# -*- coding: utf-8 -*-
import os
import traceback
import xbmc
import xbmcgui
import xbmcplugin
import unidecode
from resources.lib.utils.log_utils import get_logger

log = get_logger()


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

    title = info_labels.get('title')
    tvshowtitle = info_labels.get('tvshowtitle')
    plot = info_labels.get('plot')
    aired = info_labels.get('aired')
    mediatype = info_labels.get('mediatype')
    season = info_labels.get('season')
    episode = info_labels.get('episode')
    duration = info_labels.get('duration')
    year = info_labels.get('year')

    if title:
        tag.setTitle(str(title))
    if tvshowtitle:
        try:
            tag.setTvShowTitle(str(tvshowtitle))
        except Exception:
            pass
    if plot:
        tag.setPlot(str(plot))
    if aired:
        try:
            tag.setFirstAired(str(aired))
        except Exception:
            pass
    if mediatype:
        try:
            tag.setMediaType(str(mediatype))
        except Exception:
            pass
    if season is not None:
        try:
            tag.setSeason(int(season))
        except Exception:
            pass
    if episode is not None:
        try:
            tag.setEpisode(int(episode))
        except Exception:
            pass
    if duration is not None:
        try:
            tag.setDuration(int(duration))
        except Exception:
            pass
    if year is not None:
        try:
            tag.setYear(int(year))
        except Exception:
            pass

from resources.lib.utils.ui_utils import make_static_item, clean_filename, path_exists, listdir_safe

from resources.lib.series import series_manager
from resources.lib.series import series_filter as s_filter
from resources.lib.series.search_engine import is_storable_search_query
from resources.lib.down.download_queue import load_queue
#from resources.lib.utils.app_log import write as app_log, write_json as app_log_json

# ──────────────────────────────────────────────────────────────────────────────
# Helper pro název vyhledávání (series_search + series_refresh_smart)
# ──────────────────────────────────────────────────────────────────────────────
def _safe_search_name(data: dict, fallback: str) -> str:
    """
    Primární řetězec pro Webshare: platný search_query_name → ASCII z original_title → TMDb title → name → fallback,
    pokud fallback není úložný klíč (tmdb_<číslo> / čisté číslo).
    """
    if not isinstance(data, dict):
        data = {}
    sqn = data.get('search_query_name')
    if sqn and is_storable_search_query(sqn):
        log.debug(f"Používám prioritní název z cache: '{sqn}'")
        return sqn.strip()

    tm = data.get('tmdb') or {}
    orig = tm.get('original_title') or ''
    title = tm.get('title') or ''
    display_name = (data.get('name') or '').strip()

    if orig:
        try:
            ascii_version = unidecode.unidecode(orig).strip()
            if ascii_version and len(ascii_version) >= 3:
                log.debug(f"Používám original_title (ASCII): '{ascii_version}'")
                return ascii_version
        except Exception:
            pass

    if title:
        log.debug(f"Používám TMDb title: '{title}'")
        return title
    if display_name:
        log.debug(f"Používám display name: '{display_name}'")
        return display_name
    if fallback and is_storable_search_query(fallback):
        log.debug(f"Používám fallback: '{fallback}'")
        return fallback
    log.debug(f"Používám nouzový fallback: '{fallback}'")
    return fallback or ''


def _clean_title_str(v):
    """
    Hodnota z URL nebo TMDb: prázdné, 'None', 'null' (jakýkoli case) → None.
    """
    if v is None:
        return None
    if not isinstance(v, str):
        v = str(v)
    t = v.strip()
    if not t or t.lower() in ('none', 'null'):
        return None
    return t


def _parse_tmdb_id_param(v):
    """Číslo TMDb z URL; neplatné / prázdné → None."""
    if v is None:
        return None
    s = str(v).strip()
    if not s or s.lower() in ('none', 'null'):
        return None
    try:
        tid = int(s)
        return tid if tid > 0 else None
    except (ValueError, TypeError):
        return None


def _invalidate_series_filtered_cache():
    """Invaliduje cache filtrovaných seznamů seriálů.

    Používá stejné klíče jako `create_series_menu_filtered`, aby se po změně
    knihovny okamžitě propsal obsah sekcí Nově zařazené / Rozkoukané /
    Dosledované.
    """
    try:
        from resources.lib.cache.menu_cache_server import get_menu_cache_server
        from resources.lib.cache.menu_cache_config import log_cache_event

        cache_server = get_menu_cache_server()
        keys = (
            'series_filtered_menu_v1_new',
            'series_filtered_menu_v1_in_progress',
            'series_filtered_menu_v1_completed',
        )
        for key in keys:
            cache_server.invalidate_menu(key)
            log_cache_event('INVALIDATE', 'series_filtered_menu', f"key={key}")
    except Exception as e:
        log.warning(f"[series_cache] Invalidate series_filtered_menu selhalo: {e}")


def _fetch_tv_show_titles_from_tmdb(addon, tmdb_tv_id: int) -> dict:
    """
    Načte lokalizované názvy seriálu z TMDb (cs-CZ + en-US).
    Vrací jen neprázdné klíče: title_cs, original_name (jazyk originálu), title_en (en-US name).
    """
    out = {}
    if not addon or not tmdb_tv_id:
        return out
    api_key = addon.getSetting('tmdb_api_key')
    if not api_key:
        return out
    from resources.lib.tmdb.tmdb_module import TMDb, API_URL
    tmdb = TMDb(api_key)
    try:
        r_cs = tmdb._call_api_raw(f"{API_URL}/tv/{int(tmdb_tv_id)}", {'language': 'cs-CZ'})
        d_cs = r_cs.json()
        tc = _clean_title_str(d_cs.get('name'))
        on = _clean_title_str(d_cs.get('original_name'))
        if tc:
            out['title_cs'] = tc
        if on:
            out['original_name'] = on
    except Exception as e:
        log.warning(f"[series_search] TMDb tv/{tmdb_tv_id} cs-CZ: {e}")
    try:
        r_en = tmdb._call_api_raw(f"{API_URL}/tv/{int(tmdb_tv_id)}", {'language': 'en-US'})
        d_en = r_en.json()
        te = _clean_title_str(d_en.get('name'))
        if te:
            out['title_en'] = te
    except Exception as e:
        log.warning(f"[series_search] TMDb tv/{tmdb_tv_id} en-US: {e}")
    return out


def _build_smart_refresh_queries(data: dict, storage_key: str) -> list:
    """
    Kandidátní Webshare dotazy pro smart refresh: nejdřív uložené varianty z prvního hledání
    (extras.webshare_query_variants), pak TMDb title/name/original + ASCII original + search_query_name.
    Řeší případ, kdy search_query_name i tmdb.title zůstanou jen krátké (např. „Marshals“) a zmizí dlouhý CZ název z URL.
    """
    if not isinstance(data, dict):
        data = {}
    out = []
    seen_lower = set()

    def add(q):
        if not q or not isinstance(q, str):
            return
        t = q.strip()
        if not is_storable_search_query(t):
            return
        k = t.lower()
        if k in seen_lower:
            return
        seen_lower.add(k)
        out.append(t)

    variants = (data.get('extras') or {}).get('webshare_query_variants')
    if isinstance(variants, list):
        for q in variants:
            add(q)

    tm = data.get('tmdb') or {}
    add(tm.get('title') or '')
    add((data.get('name') or '').strip())
    add(tm.get('original_title') or '')
    orig = tm.get('original_title') or ''
    if orig:
        try:
            asc = unidecode.unidecode(orig).strip()
            if asc and len(asc) >= 3:
                add(asc)
        except Exception:
            pass
    add((data.get('search_query_name') or '').strip())
    if storage_key:
        add(storage_key)

    if not out:
        out.append(_safe_search_name(data, storage_key))
    return out


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────

def _get_effective_max_streams(series_data, addon):
    """Vrátí platný limit streamů na epizodu.

    Priorita:
    1. Per-seriál override: ``extras['max_streams_override']`` (int > 0)
    2. Globální nastavení: ``series_max_streams_per_episode``
    3. Fallback: 50
    """
    try:
        override = int((series_data.get('extras') or {}).get('max_streams_override') or 0)
        if override > 0:
            return override
    except (TypeError, ValueError):
        pass
    try:
        global_val = int(addon.getSetting('series_max_streams_per_episode') or 50)
        if global_val > 0:
            return global_val
    except (TypeError, ValueError):
        pass
    return 50


# ──────────────────────────────────────────────────────────────────────────────
# Funkce pro vyhledávání seriálu
# ──────────────────────────────────────────────────────────────────────────────
# resources/lib/series/series_ui.py (KOMPLETNÍ FUNKCE series_search)

def series_search(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """Search for a TV series and organize it into seasons and episodes"""
    token = revalidate()
    
    # 1. Získání názvů
    series_name = params.get('what') or xbmcgui.Dialog().input("Zadejte název seriálu")

    fallback_series_name = _clean_title_str(params.get('fallback_series_name'))
    tmdb_id_param = params.get('tmdb_id')
    log.info(f"Params received. Primary: '{series_name}', Fallback: '{fallback_series_name}', TMDb ID: '{tmdb_id_param}'")

    if not series_name:

        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name) or {}

    # --- TMDb API: doplnění CZ/EN názvů podle tmdb_id (když v JSON/URL chybí) ---
    tmdb_tv_id = _parse_tmdb_id_param(tmdb_id_param)
    api_titles = _fetch_tv_show_titles_from_tmdb(addon, tmdb_tv_id) if tmdb_tv_id else {}
    tmdb_merge = dict((data.get('tmdb') or {}) if isinstance(data, dict) else {})
    if api_titles:
        if not _clean_title_str(tmdb_merge.get('title')) and api_titles.get('title_cs'):
            tmdb_merge['title'] = api_titles['title_cs']
        if not _clean_title_str(tmdb_merge.get('original_title')):
            for cand in (api_titles.get('original_name'), api_titles.get('title_en')):
                c = _clean_title_str(cand)
                if c:
                    tmdb_merge['original_title'] = c
                    break
        if isinstance(data, dict):
            data['tmdb'] = tmdb_merge
        log.info(f"[series_search] Po doplnění z TMDb tv/{tmdb_tv_id}: {api_titles}")

    # --- Nabídka volby CZ vs EN název ---
    tmdb = tmdb_merge
    cz_title = (
        _clean_title_str(tmdb.get('title'))
        or _clean_title_str(data.get('name') if isinstance(data, dict) else None)
        or api_titles.get('title_cs')
        or series_name
    )
    en_title = _clean_title_str(tmdb.get('original_title'))
    if not en_title:
        en_title = fallback_series_name
    if not en_title:
        en_title = _clean_title_str(api_titles.get('original_name')) or _clean_title_str(api_titles.get('title_en'))

    log.info(f"CZ='{cz_title}' ENG='{en_title}'")

    chosen_search_name = None  # výsledek volby uživatele (nebo parametru); None -> použije se autodetekce


    # Pokud není prefer_lang, nabídneme volbu (jen když máme oba názvy a liší se)
    if chosen_search_name is None:
        try:
            if en_title and en_title.strip() and en_title.strip().lower() != (cz_title or '').strip().lower():
                choice = xbmcgui.Dialog().select(
                    "Vyhledat podle názvu",
                    ["Česky: " + (cz_title or ''), "Anglicky: " + en_title]
                )
                if choice == 0:
                    chosen_search_name = cz_title or series_name
                    log.info(f"Volba uživatele: CZ → '{chosen_search_name}'")
                elif choice == 1:
                    chosen_search_name = en_title
                    log.info(f"Volba uživatele: EN → '{chosen_search_name}'")
                # choice < 0 -> zavřel dialog; necháme chosen_search_name = None (autodetekce níže)

        except Exception:
            # dialog nepodporován / jiná drobná chyba -> pokračujeme autodetekcí
            pass

        # 2. Určení primárního názvu pro vyhledávání (z TMDB dat)
    search_name_primary = chosen_search_name or _safe_search_name(data, series_name)
    log.info(f"Používám primární název pro vyhledávání: '{search_name_primary}' (původní: '{series_name}')")

    # 🟢 Sběr všech potenciálních názvů (Stejná logika jako v series_refresh, ale pro první hledání)

    search_queries = []
    
    # 1. Priorita: Zvolený název uživatelem / safe search name
    search_queries.append(search_name_primary) 

    # 2. Český název (title), pokud se liší a je platný
    if cz_title and cz_title != search_name_primary:
        search_queries.append(cz_title)
    
    # 3. Originální název (original_title / fallback_series_name), pokud se liší a je platný
    en_or_fallback = en_title if en_title else fallback_series_name
    if en_or_fallback and en_or_fallback != search_name_primary and en_or_fallback != cz_title:
        search_queries.append(en_or_fallback)

    # Odstranění duplicit, prázdných a neplatných dotazů (např. řetězec "None" z URL)
    raw_order = [q for q in dict.fromkeys(search_queries) if q]
    unique_queries = [str(q).strip() for q in raw_order if is_storable_search_query(str(q).strip())]
    if not unique_queries and series_name and is_storable_search_query(series_name.strip()):
        unique_queries = [series_name.strip()]
    log.info(f"Potenciální dotazy k testování: {unique_queries}")
    if not unique_queries:
        popinfo('Neplatné názvy pro vyhledávání.', icon=xbmcgui.NOTIFICATION_WARNING)
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    requested_filter_mode = (params.get('filter_mode') or '').strip().lower()
    filter_mode = requested_filter_mode if requested_filter_mode in ('standard', 'aggressive') else 'standard'
    risk_info = s_filter.analyze_title_risk(
        series_name=series_name,
        title=cz_title,
        original_title=en_title,
        aliases=(data.get('aliases') if isinstance(data, dict) else [])
    )

    if requested_filter_mode not in ('standard', 'aggressive') and risk_info.get('is_risky'):
        risk_label = ', '.join(risk_info.get('reasons') or [])
        log.info(f"Rizikový název detekován score={risk_info.get('score')} reasons={risk_label}")
        choice = xbmcgui.Dialog().select(
            'Rizikový název seriálu',
            [
                'Standardní hledání',
                'Agresivní filtrace [Doporučeno]'
            ]
        )
        if choice < 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
            return
        filter_mode = 'standard' if choice == 0 else 'aggressive'

    log.info(f"Použitý režim hledání: {filter_mode}")

    progress = xbmcgui.DialogProgress()

    progress.create('StreamManager', f'Vyhledávám seriál {search_name_primary}...')
    
    try:
        series_data = None
        found_any = False

        # 🟢 ITERACE PŘES VŠECHNY UNIKÁTNÍ NÁZVY
        for i, query in enumerate(unique_queries):
            progress_val = int((i + 1) / len(unique_queries) * 100)
            
            log.info(f"Pokus {i+1}/{len(unique_queries)}: Hledám podle '{query}'")
            progress.update(progress_val, f'Vyhledávám seriál (pokus {i+1}/{len(unique_queries)}): {query}...')
            
            # Volání search_series. Pro první hledání není potřeba refresh=True.

            current_data = sm.search_series(
                query, 
                api, 
                token,
                max_streams=_get_effective_max_streams(data, addon),
                tmdb_id=tmdb_id_param,
                filter_mode=filter_mode
            )
            
            if current_data and current_data.get('seasons'):
                series_data = current_data # Uložíme poslední úspěšně nalezená data
                found_any = True
                # Pokračujeme, aby se potenciálně našlo více streamů i pod jinými názvy

        if not series_data or not series_data['seasons']:
            progress.close()
            popinfo('Nenalezeny žádné epizody tohoto seriálu', icon=xbmcgui.NOTIFICATION_WARNING)
            xbmcplugin.endOfDirectory(handle, succeeded=False)
            return

        # Uložit pořadí dotazů z tohoto hledání — smart refresh je znovu použije (TMDb často nemá dlouhý lokalizovaný název z URL).
        try:
            extras = series_data.setdefault('extras', {})
            extras['webshare_query_variants'] = list(unique_queries)
            sm._save_series_data(series_name, series_data)
        except Exception as ex:
            log.warning(f"Uložení webshare_query_variants selhalo: {ex}")

        _invalidate_series_filtered_cache()
        
        progress.close()
        
        # Zbytek logiky pro úspěšné nalezení
        dialog = xbmcgui.Dialog()
        message = (f"Seriál '{series_name}' byl úspěšně nalezen a uložen.\n\n"
                   f"Nalezeno {sum(len(season) for season in series_data['seasons'].values())} epizod "
                   f"v {len(series_data['seasons'])} sezónách.")
        dialog.ok(addon.getAddonInfo('name'), message)
        log.info(f"'{series_name}' nalezeno: {sum(len(s) for s in series_data['seasons'].values())} epizod (modul series)")
        
        # Vždy ukončíme directory pro aktuální handle s succeeded=False, aby Kodi 
        # nepřeplo obrazovku a zůstalo na místě (např. v seznamu TMDb/Trakt)
        xbmcplugin.endOfDirectory(handle, succeeded=False, cacheToDisc=False)

    except Exception as e:
        if 'progress' in locals() and progress:
            progress.close()
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        traceback.print_exc()
        popinfo(f'Chyba: {str(e)}', icon=xbmcgui.NOTIFICATION_ERROR)



def series_refresh_smart(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Tenký wrapper: připraví kandidátní názvy (CZ/EN/safe) a zavolá UpdateEngine.
    Původní A/B/C(+D) logika je přesunuta do resources.lib.series.update_engine.UpdateEngine.
    """
    import xbmc
    import xbmcplugin
    import xbmcgui
    from resources.lib.series import series_manager
    from resources.lib.series.update_engine import UpdateEngine

    series_name = params.get('series_name')
    if not series_name:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    token = revalidate()
    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name)
    if not data:
        popinfo(f"Seriál '{series_name}' není v databázi.", icon=xbmcgui.NOTIFICATION_WARNING)
        return

    # ---- Kandidátní dotazy: varianty z prvního hledání + TMDb / name (viz _build_smart_refresh_queries) ---------------
    tmdb = data.get('tmdb', {}) if isinstance(data, dict) else {}
    orig_title = tmdb.get('original_title') or ''
    cz_title = tmdb.get('title') or data.get('name')
    if not cz_title:
        cz_title = series_name if is_storable_search_query(series_name) else ''
    cz_title = cz_title or data.get('name') or series_name

    unique_queries = _build_smart_refresh_queries(data, series_name)
    log.info(f"Dotazy: {unique_queries}")

    requested_filter_mode = (params.get('filter_mode') or '').strip().lower()
    filter_mode = requested_filter_mode if requested_filter_mode in ('standard', 'aggressive') else 'standard'
    risk_info = s_filter.analyze_title_risk(
        series_name=series_name,
        title=cz_title,
        original_title=orig_title,
        aliases=(data.get('aliases') if isinstance(data, dict) else [])
    )

    if requested_filter_mode not in ('standard', 'aggressive') and risk_info.get('is_risky'):
        risk_label = ', '.join(risk_info.get('reasons') or [])
        log.info(f"[refresh_smart] Rizikový název detekován score={risk_info.get('score')} reasons={risk_label}")
        choice = xbmcgui.Dialog().select(
            'Rizikový název seriálu',
            [
                'Standardní hledání',
                'Agresivní filtrace [Doporučeno]'
            ]
        )
        if choice < 0:
            if handle > 0:
                xbmcplugin.endOfDirectory(handle, succeeded=False)
            return
        filter_mode = 'standard' if choice == 0 else 'aggressive'

    log.info(f"[refresh_smart] Použitý režim hledání: {filter_mode}")

    # ---- Zavolej UpdateEngine -------------------------------------------------

    engine = UpdateEngine(sm)
    result = engine.refresh_smart(
        series_name,
        api,
        token,
        unique_queries,
        max_streams=_get_effective_max_streams(data, addon),
        filter_mode=filter_mode,
        progress_label=data.get('name') or series_name,
    )

    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)

    if not result:
        popinfo('Nenalezeny žádné epizody tohoto seriálu', icon=xbmcgui.NOTIFICATION_WARNING)
        xbmc.executebuiltin('Container.Refresh')
        return

    eps_with_streams = [
        (s_k, e_k)
        for s_k, s_data in result.get('seasons', {}).items()
        for e_k, e_data in (s_data or {}).items()
        if e_k.isdigit() and e_data.get('streams')
    ]
    episode_count = len(eps_with_streams)
    season_count = len(set(s_k for s_k, e_k in eps_with_streams))
    popinfo(f"Smart aktualizace: {episode_count} epizod se streamy v {season_count} sezónách")
    xbmc.executebuiltin('Container.Refresh')


def series_refresh_episode_smart(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Cílený smart refresh pouze pro jednu epizodu.
    Používá stejný UpdateEngine jako celoseriálový refresh, ale s target S/E filtrem.
    """
    import xbmc
    import xbmcplugin
    import xbmcgui
    from resources.lib.series import series_manager
    from resources.lib.series.update_engine import UpdateEngine

    series_name = params.get('series_name')
    season_key = params.get('season')
    episode_key = params.get('episode')
    if not series_name or season_key is None or episode_key is None:
        xbmcgui.Dialog().notification(
            addon.getAddonInfo('name'),
            "Chybí parametry (seriál/sezóna/epizoda)",
            xbmcgui.NOTIFICATION_ERROR,
            2500,
        )
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    try:
        season_num = int(season_key)
        episode_num = int(episode_key)
    except (TypeError, ValueError):
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Neplatné číslo epizody", xbmcgui.NOTIFICATION_ERROR, 2500)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    token = revalidate()
    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name)
    if not data:
        popinfo(f"Seriál '{series_name}' není v databázi.", icon=xbmcgui.NOTIFICATION_WARNING)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    tmdb = data.get('tmdb', {}) if isinstance(data, dict) else {}
    orig_title = tmdb.get('original_title') or ''
    cz_title = tmdb.get('title') or data.get('name') or series_name
    unique_queries = _build_smart_refresh_queries(data, series_name)

    requested_filter_mode = (params.get('filter_mode') or '').strip().lower()
    filter_mode = requested_filter_mode if requested_filter_mode in ('standard', 'aggressive') else 'standard'
    risk_info = s_filter.analyze_title_risk(
        series_name=series_name,
        title=cz_title,
        original_title=orig_title,
        aliases=(data.get('aliases') if isinstance(data, dict) else [])
    )
    if requested_filter_mode not in ('standard', 'aggressive') and risk_info.get('is_risky'):
        choice = xbmcgui.Dialog().select(
            'Rizikový název seriálu',
            ['Standardní hledání', 'Agresivní filtrace [Doporučeno]']
        )
        if choice < 0:
            if handle > 0:
                xbmcplugin.endOfDirectory(handle, succeeded=False)
            return
        filter_mode = 'standard' if choice == 0 else 'aggressive'

    engine = UpdateEngine(sm)
    result = engine.refresh_smart(
        series_name,
        api,
        token,
        unique_queries,
        max_streams=_get_effective_max_streams(data, addon),
        filter_mode=filter_mode,
        progress_label=data.get('name') or series_name,
        target_season=season_num,
        target_episode=episode_num,
    )

    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)

    if not result:
        popinfo(f"Smart aktualizace S{season_num:02d}E{episode_num:02d}: nic nenalezeno", icon=xbmcgui.NOTIFICATION_WARNING)
    else:
        ep = ((result.get('seasons') or {}).get(str(season_num)) or {}).get(str(episode_num)) or {}
        stream_count = len(ep.get('streams') or [])
        popinfo(f"Smart aktualizace S{season_num:02d}E{episode_num:02d}: {stream_count} streamů")

    try:
        xbmc.executebuiltin(
            f'Container.Update({get_url(action="series_season", series_name=series_name, season=season_num)})'
        )
    except Exception:
        xbmc.executebuiltin('Container.Refresh')


# ──────────────────────────────────────────────────────────────────────────────
# Hromadná kontrola nových epizod (bulk — jen pro nedoodvysílané seriály)
# ──────────────────────────────────────────────────────────────────────────────

def _episode_snapshot(data: dict) -> set:
    """Vrátí set (season, episode) klíčů pro všechny číselné epizody v datech."""
    snap = set()
    if not isinstance(data, dict):
        return snap
    seasons = data.get('seasons') or {}
    for s_key, s_data in seasons.items():
        if not str(s_key).isdigit():
            continue
        if not isinstance(s_data, dict):
            continue
        for e_key in s_data.keys():
            if str(e_key).isdigit():
                snap.add((str(s_key), str(e_key)))
    return snap


def _get_show_status_safe(tmdb_id) -> str:
    """Nedestruktivně zkusí zjistit TMDb status seriálu. Vrací '' pokud selže."""
    try:
        if not tmdb_id:
            return ''
        from resources.lib.tmdb.tmdb_utils import get_tv_show_meta
        meta = get_tv_show_meta(int(tmdb_id), language='en') or {}
        return (meta.get('status') or '').strip()
    except Exception as e:
        log.debug(f"[bulk_new_eps] get_show_status failed: {e}")
        return ''


def _fmt_se(season: str, episode: str) -> str:
    try:
        return f"S{int(season):02d}E{int(episode):02d}"
    except (TypeError, ValueError):
        return f"S{season}E{episode}"


def _parse_iso_date(s):
    """Vrátí date ze stringu 'YYYY-MM-DD', jinak None."""
    try:
        if not s or not isinstance(s, str) or len(s) < 10:
            return None
        from datetime import datetime
        return datetime.strptime(s[:10], '%Y-%m-%d').date()
    except Exception:
        return None


def _find_missing_aired_episodes(data: dict):
    """Projde lokální data seriálu a vrátí seznam **(s_key, e_key)** pro epizody,
    které jsou podle `air_date` už odvysílané, ale nemají lokálně streamy.

    Zdroje air_date:
      1) ep['tmdb']['air_date']  (preferred)
      2) ep['air_date']          (fallback)

    Bere v úvahu i epizody, které chybí úplně, pokud `data['tmdb']['seasons'][s]`
    poskytuje počet dílů (episode_count) + sezónní air_date v minulosti:
    takové epizody pokládáme za "pravděpodobně odvysílané, chybějící" a vracíme je
    mezi cíle cíleného hledání.
    """
    from datetime import datetime
    if not isinstance(data, dict):
        return []
    today = datetime.now().date()
    seasons_local = data.get('seasons') or {}
    tmdb_seasons = ((data.get('tmdb') or {}).get('seasons')) or {}

    targets = []
    seen = set()

    def _add(s_key, e_key):
        key = (str(s_key), str(e_key))
        if key in seen:
            return
        seen.add(key)
        targets.append(key)

    # 1) Epizody existující lokálně, ale bez streamů a s aired air_date
    for s_key, s_data in seasons_local.items():
        if not str(s_key).isdigit() or not isinstance(s_data, dict):
            continue
        for e_key, ep in s_data.items():
            if not str(e_key).isdigit() or not isinstance(ep, dict):
                continue
            streams = ep.get('streams') or []
            if streams:
                continue
            ad_raw = ((ep.get('tmdb') or {}).get('air_date')) or ep.get('air_date')
            ad = _parse_iso_date(ad_raw)
            if ad and ad <= today:
                _add(s_key, e_key)

    # 2) Chybějící lokálně, ale TMDb říká: sezóna má víc epizod a sezónní air_date je v minulosti
    for s_key, s_meta in tmdb_seasons.items():
        if not str(s_key).isdigit() or not isinstance(s_meta, dict):
            continue
        try:
            epc = int(s_meta.get('episode_count') or 0)
        except (TypeError, ValueError):
            epc = 0
        if epc <= 0:
            continue
        s_air = _parse_iso_date(s_meta.get('air_date'))
        if not s_air or s_air > today:
            continue
        local_keys = set(e for e in (seasons_local.get(str(s_key)) or {}).keys() if str(e).isdigit())
        for e_num in range(1, epc + 1):
            if str(e_num) in local_keys:
                continue
            _add(s_key, e_num)

    # Setřídit (s, e) číselně
    targets.sort(key=lambda se: (int(se[0]), int(se[1])))
    return targets


def series_bulk_new_episodes(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """Hromadně projde všechny seriály v knihovně a **pouze pro ty nedoodvysílané**
    (= obsahují epizodu s air_date v minulosti a bez streamů) spustí **cílené**
    hledání `SxxExx` na chybějící epizody.

    - Kandidáti na seriály: `_find_missing_aired_episodes(data)` vrátí ≥ 1 cíl.
    - Stav online: `Ended`/`Canceled` seriály v TMDb se přeskočí.
    - Cílené hledání: `SearchEngine.search_series(..., refresh=True, name_only=True,
      update_policy='append')` — nedestruktivní doplnění chybějících `SxxExx`.
    - Diff: před/po volání `search_series` se porovnají (season, episode) klíče
      a hlásíme jen **nově vytvořené** epizody. Report: `Seriál — SxxExx — N streamů`.
    """
    from resources.lib.series import series_manager
    from resources.lib.series.search_engine import SearchEngine

    sm = series_manager.SeriesManager(addon, profile)

    try:
        all_series = sm.get_all_series() or []
    except Exception as e:
        log.error(f"[bulk_new_eps] get_all_series selhalo: {e}")
        all_series = []

    if not all_series:
        popinfo('Žádné seriály v knihovně.', icon=xbmcgui.NOTIFICATION_WARNING)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    total = len(all_series)
    confirm = xbmcgui.Dialog().yesno(
        'Hromadné hledání nových epizod',
        (f"Projít {total} seriálů a cíleně dohledat chybějící odvysílané epizody.\n"
         "Seriály, které mají všechny aired epizody se streamy, se přeskočí.\n"
         "Ukončené seriály (TMDb status: Ended/Canceled) se přeskočí.\n\n"
         "Operace zatíží Webshare API. Pokračovat?"),
        nolabel='Zrušit', yeslabel='Pokračovat'
    )
    if not confirm:
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    try:
        token = revalidate()
    except Exception as e:
        log.error(f"[bulk_new_eps] revalidate selhalo: {e}")
        popinfo('Nelze obnovit Webshare token.', icon=xbmcgui.NOTIFICATION_ERROR)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    se = SearchEngine(sm)
    monitor = xbmc.Monitor()

    rows_new = []             # "Seriál — SxxExx — N streamů"
    skipped_ended = []        # ukončené seriály
    skipped_complete = []     # seriály bez chybějících aired epizod
    failed_series = []        # (name, reason)
    scanned = 0
    refreshed = 0             # kolik seriálů jsme skutečně hledali
    updated_count = 0         # u kolika seriálů refresh přinesl novou epizodu
    new_eps_total = 0
    user_aborted = False

    dlg = xbmcgui.DialogProgress()
    dlg.create('StreamManager', f'Hromadná kontrola: 0 / {total}')

    for idx, entry in enumerate(all_series, start=1):
        if monitor.abortRequested() or dlg.iscanceled():
            user_aborted = True
            break

        safe_name = entry.get('safe_name') or ''
        display_name = entry.get('name') or safe_name
        if not safe_name:
            continue

        scanned += 1

        pct = int(idx / total * 100)
        dlg.update(pct, f"[{idx}/{total}] {display_name}\nKontroluji chybějící epizody...")

        try:
            data_before = sm.load_series_data(safe_name) or {}
        except Exception as e:
            log.error(f"[bulk_new_eps] load '{safe_name}' selhalo: {e}")
            failed_series.append((display_name, 'load error'))
            continue

        # 1) TMDb status gate — Ended/Canceled přeskočit
        tmdb_id = ((data_before.get('tmdb') or {}).get('id')
                   or (data_before.get('tmdb') or {}).get('tv_id'))
        status_txt = _get_show_status_safe(tmdb_id)
        if status_txt and status_txt.lower() in ('ended', 'canceled', 'cancelled'):
            log.info(f"[bulk_new_eps] SKIP(ended) '{display_name}' (TMDb status={status_txt})")
            skipped_ended.append(f"{display_name} ({status_txt})")
            continue

        # 2) Kandidátnost: jsou vůbec nějaké aired-ale-bez-streamů epizody?
        targets = _find_missing_aired_episodes(data_before)
        if not targets:
            log.info(f"[bulk_new_eps] SKIP(complete) '{display_name}' — bez chybějících aired epizod")
            skipped_complete.append(display_name)
            continue

        # 3) Cílené hledání: name_only SxxExx; SearchEngine si sám sestaví 'name SxxExx' dotazy
        #    z lokálních dat (chybějící epizody podle tmdb.seasons.episode_count).
        try:
            queries = _build_smart_refresh_queries(data_before, safe_name)
        except Exception as e:
            log.warning(f"[bulk_new_eps] _build_smart_refresh_queries selhalo pro '{display_name}': {e}")
            queries = []
        if not queries:
            failed_series.append((display_name, 'bez dotazu'))
            continue

        snap_before = _episode_snapshot(data_before)
        primary_query = queries[0]
        target_preview = ', '.join(_fmt_se(s, e) for s, e in targets[:6])
        if len(targets) > 6:
            target_preview += f' …(+{len(targets) - 6})'
        dlg.update(pct, f"[{idx}/{total}] {display_name}\nHledám: {target_preview}")
        log.info(f"[bulk_new_eps] REFRESH '{display_name}' targets={len(targets)} ({target_preview})")

        try:
            se.search_series(
                primary_query,
                api,
                token,
                refresh=True,
                name_only=True,
                update_policy='append',
                max_streams=_get_effective_max_streams(data_before, addon),
                filter_mode='standard',
                storage_key=safe_name,
            )
            refreshed += 1
        except Exception as e:
            log.error(f"[bulk_new_eps] search_series FAILED '{display_name}': {e}")
            failed_series.append((display_name, 'search error'))
            continue

        # 4) Diff pre/post + stream counts jen u nových epizod
        try:
            data_after = sm.load_series_data(safe_name) or {}
        except Exception as e:
            log.error(f"[bulk_new_eps] reload '{safe_name}' selhalo: {e}")
            failed_series.append((display_name, 'reload error'))
            continue

        snap_after = _episode_snapshot(data_after)
        new_keys = sorted(
            snap_after - snap_before,
            key=lambda se_k: (int(se_k[0]) if se_k[0].isdigit() else 9999,
                              int(se_k[1]) if se_k[1].isdigit() else 9999)
        )

        # Bonus: epizoda existovala před refresh, ale byla bez streamů — a teď je má
        #        (klíč (s,e) je v obou snapshotech, takže není v new_keys). Zařadíme ji
        #        do reportu jako "doplněnou" pro cílené chybějící aired epizody.
        seasons_before = data_before.get('seasons') or {}
        seasons_after = data_after.get('seasons') or {}
        filled_keys = []
        for s_k, e_k in targets:
            if (s_k, e_k) in new_keys:
                continue
            if (s_k, e_k) not in snap_after:
                continue
            before_streams = (((seasons_before.get(s_k) or {}).get(e_k) or {}).get('streams')) or []
            after_streams = (((seasons_after.get(s_k) or {}).get(e_k) or {}).get('streams')) or []
            if not before_streams and after_streams:
                filled_keys.append((s_k, e_k))

        all_reported = list(new_keys) + filled_keys
        if all_reported:
            updated_count += 1
            new_eps_total += len(all_reported)
            for s_k, e_k in all_reported:
                try:
                    ep = ((seasons_after.get(s_k) or {}).get(e_k) or {})
                    stream_cnt = len(ep.get('streams') or [])
                except Exception:
                    stream_cnt = 0
                tag = 'nová' if (s_k, e_k) in new_keys else 'doplněna'
                rows_new.append(f"{display_name} — {_fmt_se(s_k, e_k)} — {stream_cnt} streamů ({tag})")
            log.info(f"[bulk_new_eps] {display_name}: +{len(new_keys)} nových / "
                     f"+{len(filled_keys)} doplněných epizod")

        # Jemný throttle
        if monitor.waitForAbort(0.2):
            user_aborted = True
            break

    dlg.close()

    # ── Finální report ───────────────────────────────────────────────────────
    header_lines = []
    header_lines.append(f"Projito seriálů: {scanned}/{total}")
    if user_aborted:
        header_lines.append("[COLOR orange]Přerušeno uživatelem / systémem[/COLOR]")
    header_lines.append(f"Prohledáno (s chybějícími aired): {refreshed}")
    header_lines.append(f"Seriálů s nově nalezenou epizodou: {updated_count}")
    header_lines.append(f"Nově nalezených epizod: {new_eps_total}")
    header_lines.append(f"Přeskočeno (kompletní): {len(skipped_complete)}")
    header_lines.append(f"Přeskočeno (ukončené): {len(skipped_ended)}")
    if failed_series:
        header_lines.append(f"Chyby: {len(failed_series)}")

    body_parts = ['\n'.join(header_lines), '']

    if rows_new:
        body_parts.append('--- Nové / doplněné epizody ---')
        MAX_ROWS = 200
        if len(rows_new) > MAX_ROWS:
            body_parts.extend(rows_new[:MAX_ROWS])
            body_parts.append(f"... a dalších {len(rows_new) - MAX_ROWS} řádků (viz log)")
        else:
            body_parts.extend(rows_new)
        body_parts.append('')

    if skipped_ended:
        body_parts.append('--- Přeskočené ukončené ---')
        body_parts.extend(skipped_ended[:50])
        if len(skipped_ended) > 50:
            body_parts.append(f"... a dalších {len(skipped_ended) - 50}")
        body_parts.append('')

    if failed_series:
        body_parts.append('--- Chyby ---')
        for nm, reason in failed_series[:50]:
            body_parts.append(f"{nm}: {reason}")
        if len(failed_series) > 50:
            body_parts.append(f"... a dalších {len(failed_series) - 50}")

    summary_text = '\n'.join(body_parts).rstrip()
    log.info(f"[bulk_new_eps] REPORT: scanned={scanned}, refreshed={refreshed}, "
             f"updated={updated_count}, new_eps={new_eps_total}, "
             f"skipped_complete={len(skipped_complete)}, skipped_ended={len(skipped_ended)}, "
             f"failed={len(failed_series)}")
    for row in rows_new:
        log.info(f"[bulk_new_eps] NEW: {row}")

    try:
        xbmcgui.Dialog().textviewer('Hromadná kontrola nových epizod — report', summary_text)
    except Exception:
        xbmcgui.Dialog().ok('Hromadná kontrola', summary_text[:1500])

    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
    try:
        xbmc.executebuiltin('Container.Refresh')
    except Exception:
        pass


def series_set_max_streams(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """Nastaví per-seriál limit streamů na epizodu (extras['max_streams_override']).

    Zadání 0 nebo storno vymaže override a vrátí se na globální nastavení.
    """
    import xbmc
    import xbmcgui
    import xbmcplugin
    from resources.lib.series import series_manager

    series_name = params.get('series_name')
    if not series_name:
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name)
    if not data:
        popinfo(f"Seriál '{series_name}' není v databázi.", icon=xbmcgui.NOTIFICATION_WARNING)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    current_override = int((data.get('extras') or {}).get('max_streams_override') or 0)
    global_default = 50
    try:
        global_default = int(addon.getSetting('series_max_streams_per_episode') or 50)
    except (TypeError, ValueError):
        pass

    if current_override > 0:
        heading = f"Max streamů na epizodu (aktuálně: {current_override}, global: {global_default})"
        default_str = str(current_override)
    else:
        heading = f"Max streamů na epizodu (global: {global_default}, 0 = použít global)"
        default_str = str(global_default)

    inp = xbmcgui.Dialog().numeric(0, heading, default_str)
    if inp is None:
        # uživatel zmáčkl Cancel
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    try:
        new_val = int(inp)
    except (TypeError, ValueError):
        new_val = 0

    extras = data.setdefault('extras', {})
    if new_val <= 0:
        extras.pop('max_streams_override', None)
        log.info(f"[set_max_streams] {series_name}: override odstraněn → použije se global ({global_default})")
        popinfo(f"Limit odstraněn — použije se globální nastavení ({global_default})")
    else:
        extras['max_streams_override'] = new_val
        log.info(f"[set_max_streams] {series_name}: override nastaven na {new_val}")
        popinfo(f"Limit streamů nastaven na {new_val}")

    sm._save_series_data(series_name, data)

    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
    xbmc.executebuiltin('Container.Refresh')


# ──────────────────────────────────────────────────────────────────────────────
# Hlavní menu Seriály
# ──────────────────────────────────────────────────────────────────────────────
def series_menu(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    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
    from resources.lib.series.series_ui_builder import _restore_cached_directory_items

    xbmcplugin.setPluginCategory(handle, addon.getAddonInfo('name') + " \\\\ Seriály")
    sm = series_manager.SeriesManager(addon, profile)

    cache_server = get_menu_cache_server()
    cache_key = 'series_nav_hub_v2'
    if is_cache_enabled('series_nav_hub'):
        cached = cache_server.get_cached_menu(cache_key)
        if cached:
            log_cache_event('HIT', 'series_nav_hub', f"{cached.get('count', 0)} items")
            directory_items = _restore_cached_directory_items(cached)
            xbmcplugin.addDirectoryItems(handle, directory_items)
            xbmcplugin.setContent(handle, 'tvshows')
            xbmcplugin.endOfDirectory(handle, cacheToDisc=False)
            return
        else:
            log_cache_event('MISS', 'series_nav_hub')

    def _snap_art(li):
        art = {}
        for k in ('icon', 'thumb', 'poster', 'fanart'):
            try:
                v = li.getArt(k)
            except Exception:
                v = ''
            if v:
                art[k] = v
        return art

    directory_items = []
    serializable_items = []

    def _add(url, li, is_folder):
        lbl = li.getLabel()
        directory_items.append((url, li, is_folder))
        serializable_items.append((
            url, lbl, is_folder,
            {'title': lbl, 'plot': ''},
            _snap_art(li), {}, [], False,
        ))

    # (1) Hledat nový seriál
    li_new = make_static_item(
        label="Hledat nový seriál",
        plot=("Vyhledá nový seriál na Webshare.\n"
              "Uloží data do Správce seriálů pro další práci."),
        addon=addon,
        icon_name='search.png',
        default_icon='DefaultAddonsSearch.png'
    )
    li_new.setLabel2("Vyhledá nový seriál • uloží do Správce")
    _add(get_url(action='series_search'), li_new, False)

    # (1a) Zrušit stahování + stav fronty
    try:
        q = load_queue() or []
        downloading_count = sum(1 for i in q if (i or {}).get('status') == 'downloading')
        pending_count = sum(1 for i in q if (i or {}).get('status') == 'pending')
    except Exception:
        downloading_count = 0
        pending_count = 0

    status_tag = f"[COLOR lime]↓ {downloading_count}[/COLOR] • [COLOR gray]ve frontě: {pending_count}[/COLOR]"
    li_cancel = make_static_item(
        label=f"Zrušit stahování {status_tag}",
        plot=("Okamžitě zastaví všechny probíhající stahovací úlohy.\n"
              "Použijte pouze v případě potřeby."),
        addon=addon,
        icon_name='clear_queue.png',
        default_icon='DefaultScript.png'
    )
    li_cancel.setLabel2("Zastaví všechny stahovací úlohy")
    _add(get_url(action='cancel_download'), li_cancel, False)

    # (2) Nově zařazené
    li_new_series = make_static_item(
        label="Nově zařazené",
        plot=("Zobrazí uložené seriály, které jste zatím nezačali sledovat.\n"
              "Přehled nových seriálů ve vaší knihovně."),
        addon=addon,
        icon_name='progress.png',
        default_icon='DefaultRecentlyAddedEpisodes.png'
    )
    li_new_series.setLabel2("Seznam nesledovaných seriálů")
    _add(get_url(action='series_filtered', status='new'), li_new_series, True)

    # (3) Rozkoukané
    li_inp = make_static_item(
        label="Rozkoukané",
        plot=("Zobrazí uložené seriály, které ještě nejsou plně dosledované.\n"
              "Umožní rychlý vstup do sezón a epizod."),
        addon=addon,
        icon_name='progress.png',
        default_icon='DefaultRecentlyAddedEpisodes.png'
    )
    li_inp.setLabel2("Seznam nedosledovaných seriálů")
    _add(get_url(action='series_filtered', status='in_progress'), li_inp, True)

    # (4) Dosledované
    li_comp = make_static_item(
        label="Dosledované",
        plot=("Zobrazí uložené seriály, které jsou označené jako plně zhlédnuté.\n"
              "Přehled hotových titulů."),
        addon=addon,
        icon_name='finish.png',
        default_icon='DefaultTvShows.png'
    )
    li_comp.setLabel2("Seznam plně dosledovaných seriálů")
    _add(get_url(action='series_filtered', status='completed'), li_comp, True)

    li_next = make_static_item(
        label="Epizody ke sledování",
        plot=("Seznam přesně jedné epizody na řadě pro každý rozkoukaný seriál. "
              "Seřazeno podle toho, který seriál jste sledoval jako poslední."),
        addon=addon,
        icon_name='progress.png',
        default_icon='DefaultRecentlyAddedEpisodes.png'
    )
    li_next.setLabel2("Seznam epizod na řadě (seřazený dle aktivity)")
    _add(get_url(action='series_next_episodes'), li_next, True)

    # (4b) Hromadná kontrola nových epizod (pro neukončené seriály)
    li_bulk_new = make_static_item(
        label="Hledat nové epizody (všechny seriály)",
        plot=(
            "Hromadně projde všechny seriály v knihovně a online vyhledá\n"
            "nové epizody (smart refresh). Ukončené seriály přeskočí.\n"
            "Na konci zobrazí přehled nově nalezených epizod."
        ),
        addon=addon,
        icon_name='progress.png',
        default_icon='DefaultAddonsUpdates.png'
    )
    li_bulk_new.setLabel2("Hromadná kontrola nových epizod")
    _add(get_url(action='series_bulk_new_episodes'), li_bulk_new, False)

    # (5) Manager stažených epizod
    li_offline = make_static_item(
        label="Manager stažených",
        plot=("Zobrazí všechny stažené epizody ze složky offline.\n"
              "Umožní jednotlivé mazání nebo hromadné čištění."),
        addon=addon,
        icon_name='finish.png',
        default_icon='DefaultHardDisk.png'
    )
    li_offline.setLabel2("Správa offline stažených epizod")
    _add(get_url(action='series_offline_manager'), li_offline, True)

    if is_cache_enabled('series_nav_hub') and serializable_items:
        cache_server.cache_menu(cache_key, serializable_items, get_cache_ttl('series_nav_hub'))

    xbmcplugin.addDirectoryItems(handle, directory_items)
    try:
        xbmcplugin.setContent(handle, 'tvshows')
    except Exception:
        pass
    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)

# ──────────────────────────────────────────────────────────────────────────────
# Detail seriálu (sezóny)
# ──────────────────────────────────────────────────────────────────────────────
def series_detail(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    xbmcplugin.setPluginCategory(handle, addon.getAddonInfo('name') + " \\\\ " + params['series_name'])
    sm = series_manager.SeriesManager(addon, profile)
    series_manager.create_seasons_menu(sm, handle, params['series_name'], build_url_fn=get_url)

# ──────────────────────────────────────────────────────────────────────────────
# Detail sezóny (epizody)
# ──────────────────────────────────────────────────────────────────────────────
def series_season(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    xbmcplugin.setPluginCategory(handle, addon.getAddonInfo('name') + " \\\\ " +
                                 params['series_name'] + f" \\\\ Sezona {params['season']}")
    sm = series_manager.SeriesManager(addon, profile)
    series_manager.create_episodes_menu(sm, handle, params['series_name'], params['season'], build_url_fn=get_url)

def create_next_episodes_menu(series_manager, handle, addon, get_url, profile):
    """
    Zobrazí seznam následujících epizod (Next-Up) pro všechny rozehrané seriály.
    Integruje logiku offline detekce, kontextového menu a stavu sledování.
    """
    import xbmcplugin
    import xbmcgui
    import xbmc
    import xbmcvfs
    import xbmcaddon
    import os
    
    # Předpokládáme, že tyto helpery jsou dostupné z importů na začátku series_ui.py nebo sm3_entry.py
    builder = get_url
    ADDON = xbmcaddon.Addon()
    # Barevné tagy pro stav epizod (původně 'series_progress_color_tags')
    color_tags = True

    # 1. Načtení dat (volání metody z series_manager.py)
    sm = series_manager
    next_episodes = sm.get_next_episodes_list()
    
    # 2. Nastavení kategorie a typu obsahu
    title = "Následující epizody" 
    xbmcplugin.setPluginCategory(handle, title)
    xbmcplugin.setContent(handle, 'episodes')
    
    # 3. Příprava pro offline detekci
    dfolder = ADDON.getSetting('dfolder') or ''
    base_real = None
    if dfolder:
        # POUŽITÍ KODI FUNKCE: Správná cesta k OFFLINE SLOŽCE
        base = xbmcvfs.translatePath(dfolder)
        if base and not base.endswith(('/', '\\')):
             base += '/'
        base_real = base

    # 4. Iterace přes nalezené Next-Up epizody
    for ep in next_episodes:
        series_name = ep['series_name']
        s_num = ep.get('season_num', 0)
        e_num = ep.get('episode_num', 0)
        
        # Načíst PLNOU DATA SERIÁLU pro získání TMDB metadat a overview (pro každou iteraci)
        series_data = sm.load_series_data(series_name)
        tm_series = series_data.get('tmdb', {}) if isinstance(series_data, dict) else {}
        
        series_poster = tm_series.get('poster_url')
        series_fanart = tm_series.get('backdrop_url')
        series_overview = tm_series.get('overview') or ''
        series_year = tm_series.get('year')
        show_runtime = tm_series.get('show_runtime')

        tm_ep = ep.get('tmdb', {}) if isinstance(ep, dict) else {}
        ep_title = ep.get('name') or ep.get('title') or ep.get('source_name') or ''
        ep_plot = tm_ep.get('overview') or series_overview
        ep_still = tm_ep.get('still_url') or tm_ep.get('still_path')
        ep_air = tm_ep.get('air_date')
        ep_runtime = tm_ep.get('runtime') or show_runtime

        # Stav sledování (OK / 75% / New)
        # 🚨 Důležité: _label_suffix_for_episode je metoda SeriesManagera
        suffix = series_manager._label_suffix_for_episode(ep, color_tags) 

        # OFFLINE DETEKCE + NALEZENÍ SKUTEČNÉHO SOUBORU (Logika z create_episodes_menu)
        local_file_path = None
        is_offline = False
        
        season_folder = None
        if base_real:
            # Použít clean_filename, který musí být dostupný v rámci series_ui.py
            # Musíte zajistit, že clean_filename je importován/dostupný!
            season_folder = f"{base_real}{clean_filename(series_name)}/Sezona {s_num}/" 

        if season_folder and path_exists(season_folder):
            try:
                # Musíte zajistit, že listdir_safe je importován/dostupný!
                files = listdir_safe(season_folder) 
                patterns = []
                if ep_title:
                    # Musíte zajistit, že clean_filename je importován/dostupný!
                    patterns.append(clean_filename(ep_title).lower())
                patterns.append(f"s{int(s_num):02d}e{int(e_num):02d}".lower())
                
                for f in files:
                    name_noext = os.path.splitext(f)[0].lower()
                    if any(name_noext == p or name_noext.startswith(p) for p in patterns):
                        local_file_path = season_folder + f
                        is_offline = True
                        break
            except Exception as e:
                log.debug(f"Offline detekce selhala pro {series_name}: {e}")

        # Finální label s offline indikací

        offline_tag = " [COLOR limegreen]offline[/COLOR]" if is_offline else ""
        disp_name = ep.get('display_name') or series_name
        label = f"S{int(s_num):02d}E{int(e_num):02d} | {disp_name} - {ep_title} {suffix}{offline_tag}"
        li = xbmcgui.ListItem(label=label)

        # Artwork
        art = {}
        if ep_still:
            art.update({'thumb': ep_still, 'icon': ep_still})
        else:
            art['icon'] = 'DefaultVideo.png'
        if is_offline:
            art['thumb'] = 'DefaultHardDisk.png'
        if series_fanart:
            art['fanart'] = series_fanart
        if series_poster:
            art['poster'] = series_poster
        li.setArt(art)

        # InfoLabels
        info = {
            'title': ep_title or f"Epizoda {e_num}",
            'tvshowtitle': series_name, # Přidáno
            'plot': ep_plot,
            'aired': ep_air or '',
            'season': int(s_num),
            'episode': int(e_num),
            'mediatype': 'episode'
        }
        if isinstance(ep_runtime, int) and ep_runtime > 0:
            info['duration'] = ep_runtime
        if isinstance(series_year, int):
            info['year'] = series_year
        set_video_info(li, info)

        # Kontextové menu (přesně jako v create_episodes_menu)
        from resources.lib.utils.context_builder import build_context_menu
        item_data = {
            'series_name': series_name,
            'season': s_num,
            'episode': e_num,
            'tmdb_id': tm_series.get('id'),
            'show_title': tm_series.get('name') or '',
        }
        cm = build_context_menu(
            builder,
            item_data,
            contexts=['series_episode_item', 'titulky_episode'],
        )
        li.addContextMenuItems(cm, replaceItems=True)

        # Přehrávání vždy přes PlayHandler (online i offline):
        # pokud je dostupný offline soubor i uložené streamy, handler nabídne volbu.
        li.setProperty('IsPlayable', 'false')
        url_kwargs = {
            'action': 'play',
            'series_name': series_name,
            'season': str(s_num),
            'episode': str(e_num),
            'select': '1',
            'mediatype': 'episode',
            'offline_choice': '1',
        }
        if tm_series.get('id'):
            url_kwargs['tmdb_id'] = tm_series.get('id')
        url = builder(**url_kwargs)
        xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)

    # Nastavení zobrazení a konec adresáře
    # 🚨 Důležité: NEVYNUCOVAT ViewMode, ale pouze nastavit typ obsahu.
    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)

# ──────────────────────────────────────────────────────────────────────────────
# Odstranění seriálu
# ──────────────────────────────────────────────────────────────────────────────
def series_remove(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    series_name = params['series_name']
    tmdb_id = params.get('tmdb_id')
    status = (params.get('status') or '').strip().lower()
    from_detail = params.get('from_detail') == '1'

    if not xbmcgui.Dialog().yesno('StreamManager', f'Opravdu chcete odebrat seriál "{series_name}"?'):
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    sm = series_manager.SeriesManager(addon, profile)
    ok = sm.remove_series(series_name, tmdb_id=tmdb_id)
    if ok:
        _invalidate_series_filtered_cache()
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), 'Seriál odstraněn.', xbmcgui.NOTIFICATION_INFO, 2500)
    else:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), 'Nelze odstranit seriál.', xbmcgui.NOTIFICATION_WARNING, 2500)

    # Voláme endOfDirectory s succeeded=False, aby Kodi nepřidalo tuto akci do historie
    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)

    # Čistá navigace: parent dir nás vyhodí o krok zpět bez vytváření zacyklení
    if from_detail:
        xbmc.executebuiltin('Action(ParentDir)')
    else:
        xbmc.executebuiltin('Container.Refresh')









# ──────────────────────────────────────────────────────────────────────────────
# Odstranění sezóny
# ──────────────────────────────────────────────────────────────────────────────
def _update_series_completed_after_removal(data):
    """Udržuje agregovaný stav seriálu po ručním mazání sezón/epizod."""
    season_state = data.get('season_state') or {}
    data['series_completed'] = bool(season_state) and all(
        bool((state or {}).get('watched')) for state in season_state.values()
    )


def series_remove_season(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    series_name = params.get('series_name')
    season_key = params.get('season')
    if not series_name or not season_key:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Chybí parametry", xbmcgui.NOTIFICATION_ERROR)
        return
    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name)
    if not data:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Data seriálu nenalezena", xbmcgui.NOTIFICATION_ERROR)
        return
    if not xbmcgui.Dialog().yesno("StreamManager", f"Opravdu smazat sezónu {season_key} ze seriálu '{series_name}'?"):
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return
    try:
        if season_key in data.get('seasons', {}):
            del data['seasons'][season_key]
        if season_key in data.get('season_state', {}):
            del data['season_state'][season_key]
        _update_series_completed_after_removal(data)
        sm._save_series_data(series_name, data)
        _invalidate_series_filtered_cache()
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), f"Sezóna {season_key} odstraněna", xbmcgui.NOTIFICATION_INFO, 2500)
    except Exception as e:
        log.error(f"CHYBA: {e}")
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Chyba při mazání sezóny", xbmcgui.NOTIFICATION_ERROR)
    
    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
    xbmc.executebuiltin('Container.Refresh')




# ──────────────────────────────────────────────────────────────────────────────
# Odstranění epizody
# ──────────────────────────────────────────────────────────────────────────────
def series_remove_episode(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    series_name = params.get('series_name')
    season_key = str(params.get('season'))
    episode_key = str(params.get('episode'))
    if not series_name or not season_key or not episode_key:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Chybí parametry", xbmcgui.NOTIFICATION_ERROR)
        return
    sm = series_manager.SeriesManager(addon, profile)
    data = sm.load_series_data(series_name)
    if not data:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Data seriálu nenalezena", xbmcgui.NOTIFICATION_ERROR)
        return
    if not xbmcgui.Dialog().yesno("StreamManager",
                                  f"Opravdu smazat epizodu S{season_key}E{episode_key} ze seriálu '{series_name}'?"):
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return
    try:
        if season_key in data.get('seasons', {}) and episode_key in data['seasons'][season_key]:
            del data['seasons'][season_key][episode_key]
        sm._recalc_season_state(data, season_key)
        _update_series_completed_after_removal(data)
        sm._save_series_data(series_name, data)
        _invalidate_series_filtered_cache()
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'),
                                                                            f"Epizoda S{season_key}E{episode_key} odstraněna",
                                      xbmcgui.NOTIFICATION_INFO, 2500)
    except Exception as e:
        log.error(f"CHYBA: {e}")
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Chyba při mazání epizody", xbmcgui.NOTIFICATION_ERROR)
    
    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
    xbmc.executebuiltin('Container.Refresh')



def series_detect_czsubs_season(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Spustí sezónní fallback detekci CZ titulků (B).
    - Bez settings: skenuje VŠECHNY streamy epizody.
    - Loguje vždy (LOGINFO/LOGDEBUG), lze později zakomentovat.
    """
    import xbmc, xbmcgui
    from resources.lib.series import series_manager
    from resources.lib.series.subs_probe import scan_season_for_cz_subs

    series_name = params.get('series_name')
    season_key = params.get('season')
    if not series_name or not season_key:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'),
                                      "Chybí parametry (seriál/sezóna)",
                                      xbmcgui.NOTIFICATION_ERROR, 2500)
        return

    token = revalidate()
    sm = series_manager.SeriesManager(addon, profile)

    try:
        # 0 => všechny streamy v epizodě
        scan_season_for_cz_subs(
            sm, series_name, int(season_key), api, token,
            max_streams_per_episode=0
        )
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'),
                                      "Detekce CZ titulků dokončena",
                                      xbmcgui.NOTIFICATION_INFO, 2500)
    except Exception as e:
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'),
                                      f"Chyba detekce: {e}",
                                      xbmcgui.NOTIFICATION_ERROR, 3000)
        return

    # Refresh sezóny, ať se projeví CZtit
    try:
        xbmc.executebuiltin(f'Container.Update({get_url(action="series_season", series_name=series_name, season=season_key)})')
    except Exception:
        pass

        # --- NOVĚ: automatická Smart Aktualizace (aby se JSON seriálu přepsal podle cache) ---
    try:
        log.info(f"Auto-SmartRefresh start for '{series_name}' S{season_key}")
        xbmc.executebuiltin(
            f'RunPlugin({get_url(action="series_refresh_smart", series_name=series_name)})'
        )
    except Exception as e:
        log.warning(f"Auto-SmartRefresh FAILED: {e}")


def series_detect_cz_episode(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Cílená detekce CZdab/CZtit pouze pro jednu epizodu.
    """
    import xbmc, xbmcgui, xbmcplugin
    from resources.lib.series import series_manager
    from resources.lib.series.subs_probe import scan_episode_for_cz_tags

    series_name = params.get('series_name')
    season_key = params.get('season')
    episode_key = params.get('episode')
    if not series_name or season_key is None or episode_key is None:
        xbmcgui.Dialog().notification(
            addon.getAddonInfo('name'),
            "Chybí parametry (seriál/sezóna/epizoda)",
            xbmcgui.NOTIFICATION_ERROR,
            2500,
        )
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    try:
        season_num = int(season_key)
        episode_num = int(episode_key)
    except (TypeError, ValueError):
        xbmcgui.Dialog().notification(addon.getAddonInfo('name'), "Neplatné číslo epizody", xbmcgui.NOTIFICATION_ERROR, 2500)
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    token = revalidate()
    sm = series_manager.SeriesManager(addon, profile)

    try:
        result = scan_episode_for_cz_tags(
            sm,
            series_name,
            season_num,
            episode_num,
            api,
            token,
            max_streams=0,
        )
    except Exception as e:
        xbmcgui.Dialog().notification(
            addon.getAddonInfo('name'),
            f"Chyba detekce: {e}",
            xbmcgui.NOTIFICATION_ERROR,
            3000,
        )
        if handle > 0:
            xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    found = []
    if result.get('found_dab'):
        found.append('CZdab')
    if result.get('found_tit'):
        found.append('CZtit')
    suffix = ', '.join(found) if found else 'CZ stopa nenalezena'
    xbmcgui.Dialog().notification(
        addon.getAddonInfo('name'),
        f"S{season_num:02d}E{episode_num:02d}: {suffix}",
        xbmcgui.NOTIFICATION_INFO,
        3000,
    )

    if handle > 0:
        xbmcplugin.endOfDirectory(handle, succeeded=False)

    try:
        xbmc.executebuiltin(
            f'Container.Update({get_url(action="series_season", series_name=series_name, season=season_num)})'
        )
    except Exception:
        xbmc.executebuiltin('Container.Refresh')


# ──────────────────────────────────────────────────────────────────────────────
# Manager stažených epizod
# ──────────────────────────────────────────────────────────────────────────────
def _offline_manager_refresh_url(get_url, series_key=None):
    if series_key:
        return get_url(action='series_offline_manager_series', series_key=series_key)
    return get_url(action='series_offline_manager')


def series_offline_manager(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    První úroveň: seznam seriálů podle klíče v offline_cache (složka / label).
    Druhá úroveň: series_offline_manager_series.
    """
    import xbmcvfs

    xbmcplugin.setPluginCategory(
        handle, addon.getAddonInfo('name') + " \\\\ Manager stažených"
    )

    dfolder = addon.getSetting('dfolder') or ''
    if not dfolder or not xbmcvfs.exists(dfolder):
        xbmcgui.Dialog().ok(
            'Manager stažených',
            'Složka offline stahování není nastavena nebo neexistuje.',
        )
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    from resources.lib.database.metadata_db import db

    try:
        cache = db.get_offline_cache_all() or {}
    except Exception as e:
        log.error(f"Chyba při načítání offline cache do správce: {e}")
        cache = {}

    series_keys = sorted(
        [k for k, items in cache.items() if items],
        key=lambda x: (x or '').lower(),
    )
    total_eps = sum(len(cache.get(k) or []) for k in series_keys)

    if not series_keys:
        li_empty = xbmcgui.ListItem(label='Žádné stažené epizody')
        li_empty.setArt({'icon': 'DefaultHardDisk.png'})
        xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), li_empty, isFolder=False)
        xbmcplugin.setContent(handle, 'files')
        xbmcplugin.endOfDirectory(handle, succeeded=True, cacheToDisc=False)
        return

    header = xbmcgui.ListItem(
        label=f"[COLOR lime]{len(series_keys)} seriálů • {total_eps} epizod[/COLOR]"
    )
    header.setArt({'icon': 'DefaultHardDisk.png'})
    xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), header, isFolder=False)

    from resources.lib.utils.context_builder import build_context_menu

    for sk in series_keys:
        n = len(cache.get(sk) or [])
        li = xbmcgui.ListItem(label=f"{sk}  ({n} ep.)")
        li.setProperty('IsPlayable', 'false')
        li.setArt({'icon': 'DefaultFolder.png'})
        set_video_info(
            li,
            {
                'title': sk,
                'plot': f"Stažené epizody: {n}\n\nVstup = seznam souborů.",
                'mediatype': 'video',
            },
        )
        cm = build_context_menu(
            get_url,
            {'series_key': sk},
            contexts=['series_downloaded_manager_series'],
        )
        li.addContextMenuItems(cm, replaceItems=False)
        url = get_url(action='series_offline_manager_series', series_key=sk)
        xbmcplugin.addDirectoryItem(handle, url, li, isFolder=True)

    xbmcplugin.setContent(handle, 'files')
    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)


def series_offline_manager_series(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Druhá úroveň: epizody jednoho seriálu (klíč offline_cache). Mazání jednotlivých položek jako dřív.
    """
    import xbmcvfs

    series_key = (params.get('series_key') or '').strip()
    xbmcplugin.setPluginCategory(
        handle,
        addon.getAddonInfo('name') + " \\\\ Manager stažených \\\\ " + series_key,
    )

    dfolder = addon.getSetting('dfolder') or ''
    if not dfolder or not xbmcvfs.exists(dfolder):
        xbmcgui.Dialog().ok(
            'Manager stažených',
            'Složka offline stahování není nastavena nebo neexistuje.',
        )
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    if not series_key:
        xbmcplugin.endOfDirectory(handle, succeeded=False)
        return

    from resources.lib.database.metadata_db import db

    try:
        cache = db.get_offline_cache_all() or {}
        raw_items = cache.get(series_key) or []
    except Exception as e:
        log.error(f"Chyba při načítání offline cache (seriál): {e}")
        raw_items = []

    episodes = []
    for item in raw_items:
        season = item.get('season', 0)
        ep_num = item.get('episode', 0)
        path = item.get('path', '')
        if not path:
            continue
        filename = os.path.basename(path)
        episodes.append({
            'filename': filename,
            'path': path,
            'label': f"S{int(season):02d}E{int(ep_num):02d} - {filename}",
        })

    if not episodes:
        li_empty = xbmcgui.ListItem(label='Žádné soubory pro tento seriál')
        li_empty.setArt({'icon': 'DefaultHardDisk.png'})
        xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), li_empty, isFolder=False)
        xbmcplugin.setContent(handle, 'files')
        xbmcplugin.endOfDirectory(handle, succeeded=True, cacheToDisc=False)
        return

    header = xbmcgui.ListItem(
        label=f"[COLOR lime]{series_key}: {len(episodes)} epizod[/COLOR]"
    )
    header.setArt({'icon': 'DefaultHardDisk.png'})
    xbmcplugin.addDirectoryItem(handle, get_url(action='noop'), header, isFolder=False)

    from resources.lib.utils.context_builder import build_context_menu

    for ep in episodes:
        li = xbmcgui.ListItem(label=ep['label'])
        li.setProperty('IsPlayable', 'false')
        li.setArt({'icon': 'DefaultVideo.png'})
        info = {
            'title': ep['filename'],
            'plot': f"Cesta: {ep['path']}\n\nStiskněte OK/Enter pro smazání.",
            'mediatype': 'video',
        }
        set_video_info(li, info)
        item_data = {'path': ep['path'], 'series_key': series_key}
        cm = build_context_menu(
            get_url, item_data, contexts=['series_downloaded_manager_item']
        )
        li.addContextMenuItems(cm, replaceItems=False)
        url = get_url(
            action='series_offline_delete',
            path=ep['path'],
            series_key=series_key,
        )
        xbmcplugin.addDirectoryItem(handle, url, li, isFolder=False)

    xbmcplugin.setContent(handle, 'files')
    xbmcplugin.endOfDirectory(handle, cacheToDisc=False)


def _offline_parent_dir(path: str) -> str:
    """Vrátí rodičovský adresář cesty v VFS-kompatibilním tvaru (forward slash,
    bez koncového lomítka). Funguje pro smíšené separátory z listdir/stat."""
    if not path:
        return ""
    p = str(path).replace('\\', '/').rstrip('/')
    idx = p.rfind('/')
    return p[:idx] if idx > 0 else ""


def _offline_rmdir_if_empty(folder: str) -> bool:
    """Smaže složku, pokud je prázdná (bez podsložek i souborů).
    Kompatibilní s SMB/NFS přes xbmcvfs. Vrací True pokud je po zavolání
    složka pryč (nebo nikdy neexistovala)."""
    import xbmcvfs
    if not folder:
        return False
    try:
        probe = folder if folder.endswith(('/', '\\')) else folder + '/'
        if not xbmcvfs.exists(probe):
            return True
        dirs, files = xbmcvfs.listdir(probe)
        if dirs or files:
            log.debug(f"[_offline_rmdir_if_empty] '{folder}' není prázdná (dirs={len(dirs)}, files={len(files)})")
            return False
        ok = bool(xbmcvfs.rmdir(probe))
        if not ok:
            ok = bool(xbmcvfs.rmdir(folder))
        if ok:
            log.info(f"[_offline_rmdir_if_empty] Smazána prázdná složka: {folder}")
        else:
            log.warning(f"[_offline_rmdir_if_empty] xbmcvfs.rmdir selhal: {folder}")
        return ok
    except Exception as e:
        log.warning(f"[_offline_rmdir_if_empty] {folder}: {e}")
        return False


def _offline_cleanup_empty_folders(paths: list) -> dict:
    """Po smazání souborů se pokusí odstranit prázdné sezónní složky a pak
    prázdnou složku seriálu. Bere unikátní rodiče z předaných cest souborů.
    Vrací dict se statistikami pro notifikaci / log."""
    season_dirs = set()
    for p in paths or []:
        d = _offline_parent_dir(p)
        if d:
            season_dirs.add(d)

    stats = {'season_removed': 0, 'season_kept': 0, 'series_removed': 0, 'series_kept': 0}
    series_dirs = set()
    for sd in season_dirs:
        series_dirs.add(_offline_parent_dir(sd))
        if _offline_rmdir_if_empty(sd):
            stats['season_removed'] += 1
        else:
            stats['season_kept'] += 1

    for rd in series_dirs:
        if not rd:
            continue
        if _offline_rmdir_if_empty(rd):
            stats['series_removed'] += 1
        else:
            stats['series_kept'] += 1

    log.info(f"[_offline_cleanup_empty_folders] {stats}")
    return stats


def series_offline_delete_series(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """Smaže všechny stažené soubory pro jeden klíč seriálu v offline_cache + řádky v DB."""
    import xbmcvfs

    series_key = (params.get('series_key') or '').strip()
    if not series_key:
        return

    from resources.lib.database.metadata_db import db

    items = db.get_offline_cache_for_series(series_key) or []
    if not items:
        xbmcgui.Dialog().notification(
            'Manager stažených',
            'Žádné záznamy v cache.',
            xbmcgui.NOTIFICATION_WARNING,
        )
        return

    if not xbmcgui.Dialog().yesno(
        'Manager stažených',
        f'Opravdu smazat celý seriál z disku?\n\n{series_key}\n({len(items)} souborů)',
    ):
        return

    deleted = 0
    errors = 0
    deleted_paths = []
    for it in items:
        path = (it.get('path') or '').strip()
        if not path:
            continue
        try:
            if xbmcvfs.exists(path):
                if xbmcvfs.delete(path):
                    deleted += 1
                    deleted_paths.append(path)
                else:
                    errors += 1
            else:
                deleted += 1
                deleted_paths.append(path)
        except Exception as e:
            log.warning(f"[series_offline_delete_series] {path}: {e}")
            errors += 1

    folder_stats = _offline_cleanup_empty_folders(deleted_paths)

    try:
        db.clear_offline_cache_for_series(series_key)
    except Exception as e:
        log.error(f"clear_offline_cache_for_series: {e}")

    try:
        from resources.lib.utils.offline_scanner import run_offline_scan

        run_offline_scan(force=True)
    except Exception as e:
        log.error(f"run_offline_scan: {e}")

    msg = f'Smazáno: {deleted}'
    if errors:
        msg += f' (chyby: {errors})'
    if folder_stats['season_removed'] or folder_stats['series_removed']:
        msg += f" | složek: {folder_stats['season_removed']}+{folder_stats['series_removed']}"
    xbmcgui.Dialog().notification('Manager stažených', msg, xbmcgui.NOTIFICATION_INFO, 3500)
    xbmc.executebuiltin(
        f'Container.Update({_offline_manager_refresh_url(get_url, None)})'
    )


def series_offline_delete(params, addon, handle, profile, get_url, api, revalidate, popinfo):
    """
    Smaže jednu staženou epizodu s potvrzením.
    """
    import xbmcvfs
    
    path = params.get('path', '')
    if not path or not xbmcvfs.exists(path):
        xbmcgui.Dialog().notification('Manager stažených', 'Soubor nenalezen.', xbmcgui.NOTIFICATION_WARNING)
        return
    
    # Získat název souboru pro dialog
    filename = os.path.basename(path)
    
    # Potvrzení
    if not xbmcgui.Dialog().yesno('Manager stažených', f'Opravdu smazat?\n\n{filename}'):
        return
    
    # Smazat
    try:
        success = xbmcvfs.delete(path)
        if success:
            _offline_cleanup_empty_folders([path])
            xbmcgui.Dialog().notification('Manager stažených', 'Epizoda smazána.', xbmcgui.NOTIFICATION_INFO, 2000)
            # Vynutit update cache a UI
            try:
                from resources.lib.utils.offline_scanner import run_offline_scan
                run_offline_scan(force=True)
            except Exception as e:
                log.error(f"Nelze vynutit offline_scan: {e}")
                
            # Refresh seznamu (kořen nebo podmenu seriálu)
            sk = (params.get('series_key') or '').strip()
            xbmc.executebuiltin(
                f'Container.Update({_offline_manager_refresh_url(get_url, sk or None)})'
            )
        else:
            xbmcgui.Dialog().notification('Manager stažených', 'Smazání selhalo.', xbmcgui.NOTIFICATION_ERROR)
    except Exception as e:
        log.error(f"[OfflineManager] Delete error: {e}")
        xbmcgui.Dialog().notification('Manager stažených', f'Chyba: {e}', xbmcgui.NOTIFICATION_ERROR)


