# default.py
# coding: utf-8

import sys
import os
import json
import urllib.parse
import traceback

import xbmc
import xbmcgui
import xbmcplugin
import xbmcaddon
import xbmcvfs

import gb_common as common  # Gemeinsame Einstellungen und Funktionen

# ---- Addon / Pfade ----------------------------------------------------------
addon       = common.addon
addon_path  = common.addon_path
error_icon  = addon.getAddonInfo("icon")

def _p(*parts):
    return os.path.join(addon_path, *parts)

def _fallback(path, fallback):
    return path if path and xbmcvfs.exists(path) else fallback

filme_icon     = _fallback(_p("movies.png"), error_icon)
serien_icon    = _fallback(_p("series.png"), error_icon)
favoriten_icon = _fallback(_p("favorites.png"), error_icon)
fanart_image   = _fallback(_p("fanart.png"), addon.getAddonInfo("fanart") or error_icon)

# ---- externe Module laden (resources/lib) -----------------------------------
try:
    lib_path = os.path.join(addon_path, 'resources', 'lib')
    if lib_path not in sys.path:
        sys.path.append(lib_path)
    import favorites
    import movies
    import series
    import settings
except ImportError as e:
    xbmc.log(f"[{common.addon_id}] FEHLER: Bibliotheken konnten nicht importiert werden: {e}", xbmc.LOGERROR)
    xbmcgui.Dialog().notification("Addon Fehler", "Wichtige Bibliotheken fehlen!", error_icon)
    sys.exit(1)
except Exception as e:
    xbmc.log(f"[{common.addon_id}] Unerwarteter Fehler beim Importieren: {e}", xbmc.LOGERROR)
    xbmc.log(traceback.format_exc(), xbmc.LOGERROR)
    xbmcgui.Dialog().notification("Addon Fehler", "Unerwarteter Init-Fehler!", error_icon)
    sys.exit(1)

# ---- Helpers ----------------------------------------------------------------
LOGP = f"[{common.addon_id} default.py]"

def _log_ignored_exception(context, exc):
    try:
        xbmc.log(f"{LOGP} {context}: {exc}", xbmc.LOGDEBUG)
    except Exception:
        pass


# Optional: Router-Log/Overhead reduzieren (Settings-Key "debug" – Typ kann string/bool sein)
def _get_debug_router():
    try:
        v = addon.getSetting("debug")
    except Exception as e:
        _log_ignored_exception("getSetting('debug') failed", e)
        return False
    # Kodi liefert oft Strings ("true"/"false"/"1"/"0")
    if isinstance(v, bool):
        return v
    v = str(v).strip().lower()
    return v in ("1", "true", "yes", "on")

DEBUG_ROUTER = _get_debug_router()
def log(msg, lvl=xbmc.LOGINFO):
    # INFO, damit du es im normalen kodi.log siehst
    try:
        xbmc.log(f"{LOGP} {msg}", lvl)
    except Exception:
        pass

def notify(title, message, icon=error_icon, time=3000, sound=False):
    xbmcgui.Dialog().notification(title, message, icon, time, sound)

def parse_params():
    """Liest Plugin-Query (?a=1&b=2) in ein Dict."""
    if len(sys.argv) > 2 and sys.argv[2].startswith('?'):
        return dict(urllib.parse.parse_qsl(sys.argv[2][1:], keep_blank_values=True))
    return {}

def end_dir_fail():
    try:
        if common.addon_handle >= 0:
            xbmcplugin.endOfDirectory(common.addon_handle, succeeded=False)
    except Exception as e:
        log(f"end_dir_fail: endOfDirectory fehlgeschlagen (handle={common.addon_handle}): {e}", xbmc.LOGWARNING)

def end_dir_ok(cache=False):
    """Sauberer Abschluss, auch wenn handle=-1 (dann wird gefallbackt/geskippt)."""
    try:
        if common.addon_handle >= 0:
            xbmcplugin.endOfDirectory(common.addon_handle, succeeded=True, cacheToDisc=cache)
    except Exception as e:
        _log_ignored_exception("end_dir_ok: endOfDirectory failed", e)

def require(params, keys, msg):
    """AND-Logik für Pflichtparameter."""
    if isinstance(keys, (list, tuple)):
        ok = all(params.get(k) not in (None, "") for k in keys)
    else:
        ok = params.get(keys) not in (None, "")
    if not ok:
        notify("Fehler", msg)
        end_dir_fail()
    return ok

def require_any(params, keys, msg):
    """OR-Logik: mindestens ein Key muss existieren."""
    ok = any(params.get(k) not in (None, "") for k in keys)
    if not ok:
        notify("Fehler", msg)
        end_dir_fail()
    return ok

def pick_first(params, keys, default=None):
    for k in keys:
        v = params.get(k)
        if v not in (None, ""):
            return v
    return default

def _short(s, n=160):
    if s is None:
        return "None"
    s = str(s)
    # Never leak embedded URL credentials in debug previews.
    try:
        s = common.mask_url(s)
    except Exception as e:
        _log_ignored_exception("_short: mask_url failed", e)
    return s if len(s) <= n else (s[:n] + "...(cut)")

def _is_pid_payload(v):
    return isinstance(v, str) and v.strip().startswith("pid:")

def _build_current_plugin_url(extra_params=None):
    """Baut die aktuelle plugin://-URL aus sys.argv erneut auf (optional mit Zusatz-Parametern)."""
    try:
        base = sys.argv[0] if len(sys.argv) > 0 else ""
        if not base:
            return ""
        params = parse_params()
        if extra_params:
            for k, v in (extra_params or {}).items():
                params[str(k)] = "" if v is None else str(v)
        return base + ("?" + urllib.parse.urlencode(params) if params else "")
    except Exception as e:
        _log_ignored_exception("_build_current_plugin_url failed", e)
        return ""

def _reenter_playmedia_for_secure_favorites():
    """Im RunPlugin-Modus (handle=-1) zuerst PlayMedia(plugin://...) versuchen.

    Ziel: Bei play_favorite_episode_secure nach Möglichkeit eine *echte* Plugin-
    Wiedergabe starten, damit Kodi im Core-Log nur die plugin://-URL sieht und
    nicht sofort den finalen HTTP-Path mit Headern.
    """
    try:
        params = parse_params()
        action = (params.get("action") or "").strip()
        if action != "play_favorite_episode_secure":
            return False
        if str(params.get("__pm") or "").strip() == "1":
            return False  # Endlosschleife vermeiden

        plugin_url = _build_current_plugin_url({"__pm": "1"})
        if not plugin_url:
            return False

        log(f"Fallback setResolvedUrl(handle=-1) -> PlayMedia(plugin={_short(plugin_url)})")
        xbmc.executebuiltin(f"PlayMedia({plugin_url})")
        return True
    except Exception as e:
        log(f"PlayMedia-Reentry fehlgeschlagen: {e}", xbmc.LOGWARNING)
        return False

# ---- WICHTIG: Fallback für RunPlugin/Handle=-1 ------------------------------
# Viele Random-Buttons/Skins rufen Addons via RunPlugin auf => sys.argv[1] == -1
# Wenn series/movies dann xbmcplugin.setResolvedUrl(-1, ...) nutzen, startet nix.
_ORIG_SETRESOLVED = xbmcplugin.setResolvedUrl
_ORIG_ENDDIR      = xbmcplugin.endOfDirectory
_PLUGIN_PATCH_ACTIVE = False

def _setResolvedUrl_fallback(handle, succeeded, listitem):
    try:
        h = int(handle) if handle is not None else -1
    except Exception:
        h = -1

    # Script/RunPlugin Mode
    if h < 0:
        try:
            # Für sichere Favoriten-Wiedergabe erst versuchen, die *gleiche*
            # plugin://-Route via PlayMedia erneut zu starten. So bekommt Kodi
            # typischerweise einen echten Plugin-Playback-Flow statt direktem
            # Player().play(raw_http_url).
            if _reenter_playmedia_for_secure_favorites():
                return True

            path = ""
            try:
                path = listitem.getPath()
            except Exception:
                path = ""

            if not path:
                # ein paar mögliche Properties (falls gesetzt)
                for prop in ("path", "url", "stream_url"):
                    try:
                        v = listitem.getProperty(prop)
                        if v:
                            path = v
                            break
                    except Exception:
                        pass

            if path:
                log(f"Fallback setResolvedUrl(handle={h}) -> Player().play(path={_short(path)})")
                xbmc.Player().play(path, listitem)
                return True

            log("Fallback setResolvedUrl: handle<0 aber ListItem hat keinen Path!", xbmc.LOGERROR)
        except Exception as e:
            log(f"Fallback setResolvedUrl Exception: {e}", xbmc.LOGERROR)

    # normaler Kodi-Plugin-Flow
    return _ORIG_SETRESOLVED(handle, succeeded, listitem)

def _endOfDirectory_fallback(handle, succeeded=True, updateListing=False, cacheToDisc=True):
    try:
        h = int(handle)
    except Exception:
        h = -1
    if h < 0:
        # RunPlugin => kein Directory-Ende nötig
        log(f"Skip endOfDirectory(handle={h}) im RunPlugin-Modus")
        return True
    return _ORIG_ENDDIR(handle, succeeded, updateListing, cacheToDisc)

def _install_plugin_fallback_patch():
    global _PLUGIN_PATCH_ACTIVE
    if _PLUGIN_PATCH_ACTIVE:
        return
    xbmcplugin.setResolvedUrl = _setResolvedUrl_fallback
    xbmcplugin.endOfDirectory = _endOfDirectory_fallback
    _PLUGIN_PATCH_ACTIVE = True

def _restore_plugin_fallback_patch():
    global _PLUGIN_PATCH_ACTIVE
    if not _PLUGIN_PATCH_ACTIVE:
        return
    xbmcplugin.setResolvedUrl = _ORIG_SETRESOLVED
    xbmcplugin.endOfDirectory = _ORIG_ENDDIR
    _PLUGIN_PATCH_ACTIVE = False

# ---- Menü -------------------------------------------------------------------
def main_menu():
    try:
        xbmcplugin.setPluginCategory(common.addon_handle, common.addon_name)
        xbmcplugin.setContent(common.addon_handle, "addons")
    except Exception as e:
        _log_ignored_exception("main_menu: setPluginCategory/setContent failed", e)

    li = xbmcgui.ListItem(label="Filme", offscreen=True)
    li.setArt({'thumb': filme_icon, 'icon': filme_icon, 'poster': filme_icon, 'fanart': fanart_image})
    xbmcplugin.addDirectoryItem(handle=common.addon_handle, url=common.build_url({'action': A.SHOW_FILME_ROOT}), listitem=li, isFolder=True)

    li = xbmcgui.ListItem(label="Serien", offscreen=True)
    li.setArt({'thumb': serien_icon, 'icon': serien_icon, 'poster': serien_icon, 'fanart': fanart_image})
    # WICHTIG: Kodi speichert View-/Sort-Zustände pro Pfad.
    # Für die Serien-Hauptliste hängen auf manchen Clients alte ViewModes-Einträge
    # (z.B. Name/Aufsteigend) hart am Pfad "action=show_serien" oder alten v2/v3/v4-Root-Pfaden.
    # Mit einem kleinen, stabilen Pfad-Revision-Parameter bekommt die Serienliste
    # einen frischen Kodi-Pfad, ohne die Router-Logik zu ändern.
    xbmcplugin.addDirectoryItem(
        handle=common.addon_handle,
        url=common.build_url({'action': A.SHOW_SERIEN_ROOT}),
        listitem=li,
        isFolder=True
    )

    li = xbmcgui.ListItem(label="Favoriten", offscreen=True)
    li.setArt({'thumb': favoriten_icon, 'icon': favoriten_icon, 'poster': favoriten_icon, 'fanart': fanart_image})
    xbmcplugin.addDirectoryItem(handle=common.addon_handle, url=common.build_url({'action': A.SHOW_FAV}), listitem=li, isFolder=True)

    xbmcplugin.endOfDirectory(common.addon_handle, cacheToDisc=True)

# ---- Action Konstanten ------------------------------------------------------
class A:
    SHOW_FILME            = 'show_filme'       # Legacy-Pfad (unversioniert, Altgeräte)
    SHOW_FILME_ROOT       = 'show_filme_v8'    # Aktiver Pfad (v8) – ViewModes-Cache pro Pfad
    SHOW_FILME_ROOT_OLD   = 'show_filme_v7'    # Fallback für gespeicherte v7-Links
    SHOW_FILME_ROOT_OLDER = 'show_filme_v6'    # Fallback für gespeicherte v6-Links
    # Hinweis: show_filme_v5 hat nie existiert (v5 war nur für Serien) – kein Eintrag nötig
    SHOW_SERIEN               = 'show_serien'      # Legacy-Pfad (unversioniert, Altgeräte)
    SHOW_SERIEN_ROOT          = 'show_serien_v8'   # Aktiver Pfad (v8) – ViewModes-Cache pro Pfad
    SHOW_SERIEN_ROOT_OLD      = 'show_serien_v7'   # Fallback für gespeicherte v7-Links
    SHOW_SERIEN_ROOT_OLDER    = 'show_serien_v6'   # Fallback für gespeicherte v6-Links
    SHOW_SERIEN_ROOT_OLDEST   = 'show_serien_v4'   # Fallback für gespeicherte v4-Links
    SHOW_SERIEN_ROOT_VERY_OLD = 'show_serien_v3'   # Fallback für gespeicherte v3-Links
    SHOW_SERIEN_ROOT_ULTRA_OLD= 'show_serien_v2'   # Fallback für gespeicherte v2-Links
    SHOW_FAV         = 'show_favorites'

    SHOW_SEASONS   = 'show_seasons'
    SHOW_EPISODES  = 'show_episodes'
    SHOW_FAV_SEAS  = 'show_fav_seasons'
    SHOW_FAV_EPIS  = 'show_fav_episodes'

    PLAY_MOVIE     = 'play_movie'
    PLAY_SERIES    = 'play_series'
    PLAY_TRAILER   = 'play_trailer'
    PLAY_FAV_EPIS_SECURE = 'play_favorite_episode_secure'

    TOGGLE_WATCHED_MOVIE  = 'toggle_watched_status'
    TOGGLE_WATCHED_SERIES = 'toggle_watched_status_series'

    SETTINGS_UI     = 'addon_settings'
    TEST_CONN       = 'test_connection'
    RESET_W_MOVIE   = 'reset_watched_status_movie'
    RESET_W_SERIES  = 'reset_watched_status_series'
    CLEAR_CACHE     = 'clear_cache'

    # Random-Play
    PLAY_RANDOM_MOVIE            = 'play_random_movie'
    PLAY_RANDOM_SERIES_GLOBAL    = 'play_random_series_globally'
    PLAY_RANDOM_SERIES_THIS      = 'play_random_episode_from_this_series'
    PLAY_RANDOM_SERIES_SEASON    = 'play_random_episode_from_this_season'
    PLAY_RANDOM_FAV_SEASON       = 'play_random_favorite_episode_from_season'
    PLAY_RANDOM_FAV_SERIES       = 'play_random_favorite_series'
    PLAY_RANDOM_FAV_SERIES_THIS  = 'play_random_fav_episode_from_this_series'

    # ✅ NEU: AlarmClock/RunPlugin Hooks
    MAYBE_REFRESH_MOVIES = 'maybe_refresh_movies'
    INIT_BG_MOVIES       = 'init_bg_movies'

# ---- Dispatch-Helfer --------------------------------------------------------
def _call(func):
    def _inner(params):
        return func()
    return _inner

def _call_params(func):
    def _inner(params):
        return func(params)
    return _inner

def _call_with(keys, msg, func):
    def _inner(params):
        if not require(params, keys, msg):
            return
        return func(params)
    return _inner

def _call_with_any(keys_any, msg, func_with_value):
    def _inner(params):
        if not require_any(params, keys_any, msg):
            return
        val = pick_first(params, keys_any, default=None)
        log(f"Dispatch pick_first -> keyset={keys_any}, chosen_len={len(val) if isinstance(val,str) else -1}, preview={_short(val)}")
        return func_with_value(params, val)
    return _inner

# ---- Random aus Staffel: NICHT JSON-parsen, weil data oft pid:... ist -------
def handle_random_episode_from_season(params):
    # je nach Stelle kommt data/season_data/staffel_data
    raw = pick_first(params, ('season_data', 'staffel_data', 'data'), default=None)
    log(f"RandomSeason: raw_len={len(raw) if isinstance(raw,str) else -1}, raw_preview={_short(raw)}")

    if not raw:
        notify("Fehler", "Staffeldaten fehlen (Random Season).")
        end_dir_fail()
        return

    # Wichtig: raw so durchreichen wie es ist (pid:... oder JSON)
    return series.play_random_episode_from_this_season(raw)

# ✅ NEU: maybe_refresh_movies Hook (AlarmClock -> RunPlugin)
def handle_maybe_refresh_movies(params):
    try:
        req_ts = params.get("req_ts")
        reason = params.get("reason", "")
        log(f"maybe_refresh_movies: req_ts={req_ts} reason='{reason}'", xbmc.LOGINFO)

        # movies.py kann auch sys.argv parsen, aber wir geben es explizit rein:
        movies.maybe_refresh_movies(req_ts_param=req_ts, reason=reason)
    except Exception as e:
        log(f"maybe_refresh_movies failed: {e}\n{traceback.format_exc()}", xbmc.LOGWARNING)
    finally:
        end_dir_ok(cache=False)

# optional: einmaliger BG-Refresh (falls du das irgendwo triggerst)
def handle_init_bg_movies(params):
    try:
        log("init_bg_movies: background_refresh_movies_once()", xbmc.LOGINFO)
        movies.background_refresh_movies_once()
    except Exception as e:
        log(f"init_bg_movies failed: {e}\n{traceback.format_exc()}", xbmc.LOGWARNING)
    finally:
        end_dir_ok(cache=False)

# ---- Router (Dispatch-Tabelle) ----------------------------------------------
DISPATCH = {
    # Hauptmenüs
    A.SHOW_FILME:              _call(lambda: movies.check_movies_json()),
    A.SHOW_FILME_ROOT:         _call(lambda: movies.check_movies_json()),
    A.SHOW_FILME_ROOT_OLD:     _call(lambda: movies.check_movies_json()),
    A.SHOW_FILME_ROOT_OLDER:   _call(lambda: movies.check_movies_json()),
    A.SHOW_SERIEN:             _call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT:        _call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT_OLD:    _call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT_OLDER:  _call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT_OLDEST: _call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT_VERY_OLD:_call(lambda: series.check_series_json()),
    A.SHOW_SERIEN_ROOT_ULTRA_OLD:_call(lambda: series.check_series_json()),
    A.SHOW_FAV:                _call(lambda: favorites.show_favorite_series()),

    # Auflistungen (Serien/Favoriten)
    A.SHOW_SEASONS:  _call_with_any(('data', 'serie_data', 'series_data'),
                                    "Seriendaten für Staffeln fehlen.",
                                    lambda p, v: series.show_seasons(v)),
    A.SHOW_EPISODES: _call_with_any(('data', 'season_data', 'staffel_data'),
                                    "Staffeldaten für Episoden fehlen.",
                                    lambda p, v: series.show_episodes(v)),
    A.SHOW_FAV_SEAS: _call_with_any(('data', 'serie_data', 'series_data'),
                                    "Seriendaten für Favoriten-Staffeln fehlen.",
                                    lambda p, v: favorites.show_fav_seasons(v)),
    A.SHOW_FAV_EPIS: _call_with_any(('data', 'season_data', 'staffel_data'),
                                    "Staffeldaten für Favoriten-Episoden fehlen.",
                                    lambda p, v: favorites.show_fav_episodes(v)),

    # Random Play
    A.PLAY_RANDOM_MOVIE:         _call(lambda: movies.play_random_movie()),
    A.PLAY_RANDOM_SERIES_GLOBAL: _call(lambda: series.play_random_series_globally()),

    # Wichtig: wenn pid:... => Favoriten-Handler, sonst Serien-Handler
    A.PLAY_RANDOM_SERIES_THIS:   _call_with_any(('data', 'serie_data', 'series_data'),
                                    "Seriendaten für Zufallswiedergabe fehlen (Serien).",
                                    lambda p, v: favorites.play_random_episode_from_specific_series(v) if _is_pid_payload(v)
                                                else series.play_random_episode_from_this_series(v)),

    # Neuer Favoriten-Aktionsname (aus Favoriten-Staffelansicht)
    A.PLAY_RANDOM_FAV_SERIES_THIS: _call_with_any(('data', 'serie_data', 'series_data'),
                                    "Seriendaten für Zufallswiedergabe fehlen (Favoriten-Serie).",
                                    lambda p, v: favorites.play_random_episode_from_specific_series(v)),

    # Spezialhandler: PLAY_RANDOM_SERIES_SEASON wird im Entry-Point VOR dem DISPATCH-Lookup
    # explizit behandelt (handle_random_episode_from_season). Dieser Eintrag hier wird
    # nie ausgeführt – er ist nur als Dokumentation drin, damit klar ist, dass die Action
    # bekannt ist und nicht in den "Unbekannte Aktion"-Fallback fällt.
    # A.PLAY_RANDOM_SERIES_SEASON: _call(lambda: None),  # dead code – siehe Entry-Point

    A.PLAY_RANDOM_FAV_SEASON:    _call_with_any(('data', 'season_data', 'staffel_data'),
                                    "Staffeldaten für Zufallswiedergabe fehlen (Favoriten).",
                                    lambda p, v: favorites.play_random_episode_from_season(v)),
    A.PLAY_RANDOM_FAV_SERIES:    _call(lambda: favorites.play_random_favorite_series()),

    # Wiedergabe / Trailer
    A.PLAY_SERIES: _call_with('serie_key', "Episoden-Schlüssel für Wiedergabe fehlt.",
                    lambda p: series.play_series(
                        common.strip_url_credentials(p['serie_key']),
                        p.get('title', "Episode wird geladen..."),
                        p.get('status_id') or p.get('tmdbid'),
                        p.get('video_codec') or getattr(common, 'detect_video_codec_from_path', lambda _p: None)(p['serie_key']),
                        p.get('series_tmdbid') or p.get('tmdbid')
                    )),

    # Film-Wiedergabe: original_online_key + video_codec weiterreichen
    A.PLAY_MOVIE:  _call_with('movie_key', "Film-Schlüssel für Wiedergabe fehlt.",
                    lambda p: movies.play_movie(
                        p['movie_key'],
                        p.get('title', "Film wird geladen..."),
                        original_online_key_param=p.get('original_online_key'),
                        codec_hint_param=(
                            p.get('video_codec')
                            or getattr(common, 'detect_video_codec_from_path', lambda _p: None)(p['movie_key'])
                        )
                    )),

    # Trailer: OR statt AND
    A.PLAY_TRAILER:_call_with_any(('trailer_url','trailer'),
                    "Trailer-URL fehlt.",
                    lambda p, v: movies.play_trailer(v)),

    A.PLAY_FAV_EPIS_SECURE: _call_with_any(('data',),
                    "Episodendaten für sichere Favoriten-Wiedergabe fehlen.",
                    lambda p, v: favorites.play_favorite_episode_secure(v)),

    # Watched-Status
    A.TOGGLE_WATCHED_MOVIE:  _call_with('movie_key', "Film-Schlüssel für Statusänderung fehlt.",
                            lambda p: movies.toggle_watched_status(p['movie_key'])),
    A.TOGGLE_WATCHED_SERIES: _call_with('serie_key', "Serien/Episoden-Schlüssel für Statusänderung fehlt.",
                            lambda p: series.toggle_watched_status(p['serie_key'])),

    # Settings
    A.SETTINGS_UI:   _call(lambda: xbmc.executebuiltin(f"Addon.OpenSettings({common.addon_id})")),
    A.TEST_CONN:     _call(lambda: settings.verbindungscheck()),
    A.RESET_W_MOVIE: _call(lambda: settings.reset_watched_status_movie()),
    A.RESET_W_SERIES:_call(lambda: settings.reset_watched_status_series()),
    A.CLEAR_CACHE:   _call(lambda: settings.clear_cache()),

    # ✅ NEU: AlarmClock/RunPlugin Hooks
    A.MAYBE_REFRESH_MOVIES: _call_params(handle_maybe_refresh_movies),
    A.INIT_BG_MOVIES:       _call_params(handle_init_bg_movies),
}

# ---- Entry ------------------------------------------------------------------
if __name__ == '__main__':
    _install_plugin_fallback_patch()
    try:
        # Handle bestimmen:
        current_handle = common.addon_handle

        raw_handle = sys.argv[1] if len(sys.argv) > 1 else None
        parsed_handle = None
        if isinstance(raw_handle, str) and raw_handle.lstrip("-").isdigit():
            try:
                parsed_handle = int(raw_handle)
            except Exception:
                parsed_handle = None

        if parsed_handle is not None:
            if parsed_handle >= 0:
                current_handle = parsed_handle
                common.addon_handle = parsed_handle
            else:
                log(f"Hinweis: Handle '{raw_handle}' → Script/RunPlugin-Modus (verwende {common.addon_handle}).")
        else:
            log(f"Hinweis: sys.argv[1]='{raw_handle}' ist kein numerischer Handle. Verwende {common.addon_handle}.")

        params = parse_params()
        action = (params.get('action') or '').strip()

        keys = sorted(list(params.keys()))
        if DEBUG_ROUTER:
            lens = {k: (len(params.get(k)) if isinstance(params.get(k), str) else -1) for k in keys}
            log(f"Router: Action='{action}', keys={keys}, lens={lens}")
        else:
            log(f"Router: Action='{action}', keys={keys}")

        if not action:
            main_menu()
        else:
            if action == A.PLAY_RANDOM_SERIES_SEASON:
                handle_random_episode_from_season(params)
            else:
                handler = DISPATCH.get(action)
                if handler:
                    handler(params)
                else:
                    log(f"Unbekannte Aktion '{action}' erhalten. Zeige Hauptmenü.", xbmc.LOGWARNING)
                    main_menu()

    except Exception as e:
        xbmc.log(f"[{common.addon_id}] Schwerwiegender Fehler im Haupt-Dispatcher: {e}", xbmc.LOGERROR)
        xbmc.log(traceback.format_exc(), xbmc.LOGERROR)
        notify(common.addon_name + " Fehler", f"Ein Fehler ist aufgetreten: {e}", error_icon, 5000)
        try:
            if common.addon_handle >= 0:
                xbmcplugin.endOfDirectory(common.addon_handle, succeeded=False)
        except Exception as e_end:
            xbmc.log(f"[{common.addon_id}] Fehler beim Beenden des Verzeichnisses nach Exception: {e_end}", xbmc.LOGERROR)
    finally:
        _restore_plugin_fallback_patch()
        try:
            movies.shutdown_executor()
        except Exception:
            pass