import json
import os
import threading
from datetime import datetime, timedelta
from typing import List, Optional
from urllib import parse

import xbmc
from xbmcplugin import addDirectoryItem

from lib.api.mdblist.mdblist import MDblistAPI
from lib.api.tmdbv3api.as_obj import AsObj
from lib.api.tmdbv3api.objs.anime import TmdbAnime
from lib.api.tmdbv3api.objs.collection import Collection
from lib.api.tmdbv3api.objs.discover import Discover
from lib.api.tmdbv3api.objs.episode import Episode
from lib.api.tmdbv3api.objs.find import Find
from lib.api.tmdbv3api.objs.genre import Genre
from lib.api.tmdbv3api.objs.movie import Movie
from lib.api.tmdbv3api.objs.person import Person
from lib.api.tmdbv3api.objs.search import Search
from lib.api.tmdbv3api.objs.season import Season
from lib.api.tmdbv3api.objs.trending import Trending
from lib.api.tmdbv3api.objs.tv import TV
from lib.api.tmdbv3api.tmdb import TMDb
from lib.db.cached import cache
from lib.utils.general.utils import execute_thread_pool
from lib.utils.kodi.settings import get_cache_expiration, is_cache_enabled
from lib.utils.kodi.utils import (
    ADDON_HANDLE,
    ADDON_PATH,
    action_url_run,
    container_update,
    get_setting,
    kodi_play_media,
    kodilog,
    translation,
)

LANGUAGES = [
    "ar-AE",
    "ar-SA",
    "be-BY",
    "bg-BG",
    "bn-BD",
    "ca-ES",
    "ch-GU",
    "cs-CZ",
    "da-DK",
    "de-AT",
    "de-CH",
    "de-DE",
    "el-GR",
    "en-AU",
    "en-CA",
    "en-GB",
    "en-IE",
    "en-NZ",
    "en-US",
    "eo-EO",
    "es-ES",
    "es-MX",
    "et-EE",
    "eu-ES",
    "fa-IR",
    "fi-FI",
    "fr-CA",
    "fr-FR",
    "gl-ES",
    "he-IL",
    "hi-IN",
    "hu-HU",
    "id-ID",
    "it-IT",
    "ja-JP",
    "ka-GE",
    "kk-KZ",
    "kn-IN",
    "ko-KR",
    "lt-LT",
    "lv-LV",
    "ml-IN",
    "ms-MY",
    "ms-SG",
    "nb-NO",
    "nl-NL",
    "no-NO",
    "pl-PL",
    "pt-BR",
    "pt-PT",
    "ro-RO",
    "ru-RU",
    "si-LK",
    "sk-SK",
    "sl-SI",
    "sr-RS",
    "sv-SE",
    "ta-IN",
    "te-IN",
    "th-TH",
    "tl-PH",
    "tr-TR",
    "uk-UA",
    "vi-VN",
    "zh-CN",
    "zh-HK",
    "zh-TW",
    "zu-ZA",
]

FULL_NAME_LANGUAGES = [
    {"name": "Arabic", "id": "ar"},
    {"name": "Bosnian", "id": "bs"},
    {"name": "Bulgarian", "id": "bg"},
    {"name": "Chinese", "id": "zh"},
    {"name": "Croatian", "id": "hr"},
    {"name": "Dutch", "id": "nl"},
    {"name": "English", "id": "en"},
    {"name": "Finnish", "id": "fi"},
    {"name": "French", "id": "fr"},
    {"name": "German", "id": "de"},
    {"name": "Greek", "id": "el"},
    {"name": "Hebrew", "id": "he"},
    {"name": "Hindi", "id": "hi"},
    {"name": "Hungarian", "id": "hu"},
    {"name": "Icelandic", "id": "is"},
    {"name": "Italian", "id": "it"},
    {"name": "Japanese", "id": "ja"},
    {"name": "Korean", "id": "ko"},
    {"name": "Macedonian", "id": "mk"},
    {"name": "Norwegian", "id": "no"},
    {"name": "Persian", "id": "fa"},
    {"name": "Polish", "id": "pl"},
    {"name": "Portuguese", "id": "pt"},
    {"name": "Punjabi", "id": "pa"},
    {"name": "Romanian", "id": "ro"},
    {"name": "Russian", "id": "ru"},
    {"name": "Serbian", "id": "sr"},
    {"name": "Slovenian", "id": "sl"},
    {"name": "Spanish", "id": "es"},
    {"name": "Swedish", "id": "sv"},
    {"name": "Turkish", "id": "tr"},
    {"name": "Ukrainian", "id": "uk"},
]


NETWORKS = [
    {"id": 129, "name": "A&E", "icon": "https://i.imgur.com/xLDfHjH.png"},
    {"id": 2, "name": "ABC", "icon": "https://i.imgur.com/qePLxos.png"},
    {"id": 174, "name": "AMC", "icon": "https://i.imgur.com/ndorJxi.png"},
    {"id": 2697, "name": "Acorn TV", "icon": "https://i.imgur.com/fSWB5gB.png"},
    {"id": 80, "name": "Adult Swim", "icon": "https://i.imgur.com/jCqbRcS.png"},
    {"id": 1024, "name": "Amazon", "icon": "https://i.imgur.com/ru9DDlL.png"},
    {"id": 91, "name": "Animal Planet", "icon": "https://i.imgur.com/olKc4RP.png"},
    {"id": 2552, "name": "Apple TV +", "icon": "https://i.imgur.com/fAQMVNp.png"},
    {"id": 251, "name": "Audience", "icon": "https://i.imgur.com/5Q3mo5A.png"},
    {"id": 4, "name": "BBC 1", "icon": "https://i.imgur.com/u8x26te.png"},
    {"id": 332, "name": "BBC 2", "icon": "https://i.imgur.com/SKeGH1a.png"},
    {"id": 3, "name": "BBC 3", "icon": "https://i.imgur.com/SDLeLcn.png"},
    {"id": 100, "name": "BBC 4", "icon": "https://i.imgur.com/PNDalgw.png"},
    {"id": 493, "name": "BBC America", "icon": "https://i.imgur.com/TUHDjfl.png"},
    {"id": 24, "name": "BET", "icon": "https://i.imgur.com/ZpGJ5UQ.png"},
    {"id": 74, "name": "Bravo", "icon": "https://i.imgur.com/TmEO3Tn.png"},
    {"id": 23, "name": "CBC", "icon": "https://i.imgur.com/unQ7WCZ.png"},
    {"id": 16, "name": "CBS", "icon": "https://i.imgur.com/8OT8igR.png"},
    {"id": 1709, "name": "CBS All Access", "icon": "https://i.imgur.com/ZvaWMuU.png"},
    {"id": 110, "name": "CTV", "icon": "https://i.imgur.com/qUlyVHz.png"},
    {"id": 56, "name": "Cartoon Network", "icon": "https://i.imgur.com/zmOLbbI.png"},
    {"id": 26, "name": "Channel 4", "icon": "https://i.imgur.com/6ZA9UHR.png"},
    {"id": 99, "name": "Channel 5", "icon": "https://i.imgur.com/5ubnvOh.png"},
    {"id": 359, "name": "Cinemax", "icon": "https://i.imgur.com/zWypFNI.png"},
    {"id": 47, "name": "Comedy Central", "icon": "https://i.imgur.com/ko6XN77.png"},
    {"id": 928, "name": "Crackle", "icon": "https://i.imgur.com/53kqZSY.png"},
    {"id": 2243, "name": "DC Universe", "icon": "https://i.imgur.com/bhWIubn.png"},
    {"id": 64, "name": "Discovery Channel", "icon": "https://i.imgur.com/8UrXnAB.png"},
    {"id": 244, "name": "Discovery ID", "icon": "https://i.imgur.com/07w7BER.png"},
    {"id": 4353, "name": "Discovery+", "icon": "https://i.imgur.com/ukz1nOG.png"},
    {"id": 54, "name": "Disney Channel", "icon": "https://i.imgur.com/ZCgEkp6.png"},
    {"id": 44, "name": "Disney XD", "icon": "https://i.imgur.com/PAJJoqQ.png"},
    {"id": 2739, "name": "Disney+", "icon": "https://i.imgur.com/DVrPgbM.png"},
    {"id": 76, "name": "E!", "icon": "https://i.imgur.com/3Delf9f.png"},
    {"id": 136, "name": "E4", "icon": "https://i.imgur.com/frpunK8.png"},
    {"id": 19, "name": "FOX", "icon": "https://i.imgur.com/6vc0Iov.png"},
    {"id": 88, "name": "FX", "icon": "https://i.imgur.com/aQc1AIZ.png"},
    {"id": 1267, "name": "Freeform", "icon": "https://i.imgur.com/f9AqoHE.png"},
    {"id": 49, "name": "HBO", "icon": "https://i.imgur.com/Hyu8ZGq.png"},
    {"id": 3186, "name": "HBO Max", "icon": "https://i.imgur.com/mmRMG75.png"},
    {"id": 210, "name": "HGTV", "icon": "https://i.imgur.com/INnmgLT.png"},
    {"id": 384, "name": "Hallmark Channel", "icon": "https://i.imgur.com/zXS64I8.png"},
    {"id": 65, "name": "History Channel", "icon": "https://i.imgur.com/LEMgy6n.png"},
    {"id": 453, "name": "Hulu", "icon": "https://i.imgur.com/uSD2Cdw.png"},
    {"id": 9, "name": "ITV", "icon": "https://i.imgur.com/5Hxp5eA.png"},
    {"id": 34, "name": "Lifetime", "icon": "https://i.imgur.com/tvYbhen.png"},
    {"id": 33, "name": "MTV", "icon": "https://i.imgur.com/QM6DpNW.png"},
    {"id": 6, "name": "NBC", "icon": "https://i.imgur.com/yPRirQZ.png"},
    {
        "id": 43,
        "name": "National Geographic",
        "icon": "https://i.imgur.com/XCGNKVQ.png",
    },
    {"id": 213, "name": "Netflix", "icon": "https://i.imgur.com/jI5c3bw.png"},
    {"id": 35, "name": "Nick Junior", "icon": "https://i.imgur.com/leuCWYt.png"},
    {"id": 13, "name": "Nickelodeon", "icon": "https://i.imgur.com/OUVoqYc.png"},
    {"id": 132, "name": "Oxygen", "icon": "https://i.imgur.com/uFCQvbR.png"},
    {"id": 14, "name": "PBS", "icon": "https://i.imgur.com/r9qeDJY.png"},
    {
        "id": 2076,
        "name": "Paramount Network",
        "icon": "https://i.imgur.com/ez3U6NV.png",
    },
    {"id": 4330, "name": "Paramount+", "icon": "https://i.imgur.com/dmUjWmU.png"},
    {"id": 3353, "name": "Peacock", "icon": "https://i.imgur.com/1JXFkSM.png"},
    {"id": 67, "name": "Showtime", "icon": "https://i.imgur.com/SawAYkO.png"},
    {"id": 214, "name": "Sky 1", "icon": "https://i.imgur.com/xbgzhPU.png"},
    {"id": 55, "name": "Spike", "icon": "https://i.imgur.com/BhXYytR.png"},
    {"id": 318, "name": "Starz", "icon": "https://i.imgur.com/Z0ep2Ru.png"},
    {"id": 270, "name": "SundanceTV", "icon": "https://i.imgur.com/qldG5p2.png"},
    {"id": 77, "name": "Syfy", "icon": "https://i.imgur.com/9yCq37i.png"},
    {"id": 68, "name": "TBS", "icon": "https://i.imgur.com/RVCtt4Z.png"},
    {"id": 84, "name": "TLC", "icon": "https://i.imgur.com/c24MxaB.png"},
    {"id": 41, "name": "TNT", "icon": "https://i.imgur.com/WnzpAGj.png"},
    {"id": 397, "name": "TV Land", "icon": "https://i.imgur.com/1nIeDA5.png"},
    {"id": 71, "name": "The CW", "icon": "https://i.imgur.com/Q8tooeM.png"},
    {"id": 21, "name": "The WB", "icon": "https://i.imgur.com/rzfVME6.png"},
    {"id": 209, "name": "Travel Channel", "icon": "https://i.imgur.com/mWXv7SF.png"},
    {"id": 30, "name": "USA Network", "icon": "https://i.imgur.com/Doccw9E.png"},
    {"id": 158, "name": "VH1", "icon": "https://i.imgur.com/IUtHYzA.png"},
    {"id": 202, "name": "WGN America", "icon": "https://i.imgur.com/TL6MzgO.png"},
    {"id": 1436, "name": "YouTube Red", "icon": "https://i.imgur.com/ZfewP1Y.png"},
    {"id": 364, "name": "truTV", "icon": "https://i.imgur.com/HnB3zfc.png"},
]


def add_kodi_dir_item(
    list_item, url, is_folder=True, icon_path=None, set_playable=False, batch=False
):
    if icon_path:
        add_icon_tmdb(list_item, icon_path=icon_path)
    if set_playable:
        list_item.setProperty("IsPlayable", "true")
    if batch:
        return (url, list_item, is_folder)
    addDirectoryItem(ADDON_HANDLE, url, list_item, isFolder=is_folder)


def add_icon_genre(item, icon_path="tmdb.png"):
    item.setArt(
        {
            "icon": os.path.join(ADDON_PATH, "resources", "img", icon_path),
            "thumb": os.path.join(ADDON_PATH, "resources", "img", icon_path),
        }
    )


def add_icon_tmdb(item, icon_path="tmdb.png"):
    item.setArt(
        {
            "icon": os.path.join(ADDON_PATH, "resources", "img", icon_path),
            "thumb": os.path.join(ADDON_PATH, "resources", "img", icon_path),
        }
    )


def tmdb_get(path, params=None) -> Optional[AsObj]:
    identifier = f"{path}|{params}|{TMDb().language}"
    data = cache.get(key=identifier)
    if data:
        return data

    handlers = {
        "search_tv": lambda p: Search().tv_shows(
            term=p["query"], page=p.get("page", 1), release_year=p.get("year")
        ),
        "search_movie": lambda p: Search().movies(
            term=p["query"], page=p.get("page", 1), year=p.get("year")
        ),
        "search_multi": lambda p: Search().multi(p["query"], page=p["page"]),
        "search_collections": lambda p: Search().collections(p["query"], p["page"]),
        "search_people": lambda p: Search().people(p["query"], p["page"]),
        "movie_details": lambda p: Movie().details(p["id"] if isinstance(p, dict) else p),
        "tv_details": lambda p: TV().details(p["id"] if isinstance(p, dict) else p),
        "season_details": lambda p: Season().details(p["id"], p["season"]),
        "episode_details": lambda p: Episode().details(p["id"], p["season"], p["episode"]),
        "movie_genres": lambda _: Genre().movie_list(),
        "show_genres": lambda _: Genre().tv_list(),
        "discover_movie": lambda p: Discover().discover_movies(p),
        "discover_tv": lambda p: Discover().discover_tv_shows(p),
        "tv_calendar": lambda p: Discover().discover_tv_calendar(page=p),
        "tv_week": lambda p: Trending().tv_week(page=p),
        "trending_movie": lambda p: Trending().movie_week(page=p),
        "popular_movie": lambda p: Movie().popular(page=p),
        "trending_tv": lambda p: Trending().tv_week(page=p),
        "popular_shows": lambda p: TV().popular(page=p),
        "airing_today": lambda p: TV().airing_today(page=p),
        "popular_people": lambda p: Person().popular(page=p),
        "trending_people": lambda p: Trending().person_week(page=p),
        "latest_people": lambda p: Person().latest(),
        "person_details": lambda p: Person().details(p["id"] if isinstance(p, dict) else p),
        "person_credits": lambda p: Person().combined_credits(
            p["id"] if isinstance(p, dict) else p
        ),
        "person_tv_credits": lambda p: Person().tv_credits(p["id"] if isinstance(p, dict) else p),
        "person_movie_credits": lambda p: Person().movie_credits(
            p["id"] if isinstance(p, dict) else p
        ),
        "tv_credits": lambda p: TV().credits(p["id"] if isinstance(p, dict) else p),
        "movie_credits": lambda p: Movie().credits(p["id"] if isinstance(p, dict) else p),
        "person_ids": lambda p: Person().external_ids(p["id"] if isinstance(p, dict) else p),
        "find_by_tvdb": lambda p: Find().find_by_tvdb_id(p["id"] if isinstance(p, dict) else p),
        "find_by_imdb_id": lambda p: Find().find_by_imdb_id(p["id"] if isinstance(p, dict) else p),
        "anime_year": lambda p: TmdbAnime().anime_year(p),
        "anime_genres": lambda p: TmdbAnime().anime_genres(p),
        "collection_details": lambda p: Collection().details(p["id"] if isinstance(p, dict) else p),
        "collection_images": lambda p: Collection().images(p["id"] if isinstance(p, dict) else p),
        "tv_recommendations": lambda p: TV().recommendations(tv_id=p["id"], page=p["page"]),
        "movie_recommendations": lambda p: Movie().recommendations(
            movie_id=p["id"], page=p["page"]
        ),
        "tv_similar": lambda p: TV().similar(tv_id=p["id"], page=p["page"]),
        "movie_similar": lambda p: Movie().similar(movie_id=p["id"], page=p["page"]),
        "movie_images": lambda p: Movie().images(movie_id=p["id"]),
        "tv_images": lambda p: TV().images(tv_id=p["id"]),
        "movie_reviews": lambda p: Movie().reviews(movie_id=p["id"], page=p["page"]),
        "tv_reviews": lambda p: TV().reviews(tv_id=p["id"], page=p["page"]),
        "movie_keywords": lambda p: Movie().keywords(p["id"] if isinstance(p, dict) else p),
    }

    try:
        data = handlers.get(path, lambda _: None)(params)
    except Exception as e:
        kodilog(f"Error in tmdb_get for {path} with params {params}: {e}")
        return None

    if data is not None:
        cache.set(
            key=identifier,
            data=data,
            expires=timedelta(hours=get_cache_expiration() if is_cache_enabled() else 0),
        )

    return data


def mdblist_get(path, params=None) -> Optional[List]:
    identifier = f"{path}|{params}"
    data = cache.get(key=identifier)
    if data:
        return data

    handlers = {
        "get_user_lists": lambda _: MDblistAPI().get_user_lists(),
        "get_list_items": lambda p: MDblistAPI().get_list_items(
            list_id=p.get("list_id"),
            limit=p.get("limit"),
            offset=p.get("offset"),
            append_to_response="genre,poster",
            unified=True,
        ),
        "top_mdbd_lists": lambda _: MDblistAPI().get_top_lists(),
        "search_lists": lambda p: MDblistAPI().search_lists(p["query"]),
    }

    try:
        data = handlers.get(path, lambda _: None)(params)
    except Exception as e:
        kodilog(f"Error in mdblist_get for {path} with params {params}: {e}")
        return None

    if data is not None:
        cache.set(
            key=identifier,
            data=data,
            expires=timedelta(hours=get_cache_expiration() if is_cache_enabled() else 0),
        )

    return data


def get_tmdb_media_details(tmdb_id, mode):
    if mode == "tv":
        return tmdb_get("tv_details", tmdb_id)
    elif mode == "movies":
        return tmdb_get("movie_details", tmdb_id)


def get_tmdb_movie_details(id: str) -> Optional[AsObj]:
    return tmdb_get("movie_details", id)


def get_tmdb_show_details(id: str) -> Optional[AsObj]:
    return tmdb_get("tv_details", id)


def get_movie_keywords(tmdb_id):
    """Fetch TMDB keywords for a movie. Returns list of keyword names."""
    try:
        res = tmdb_get("movie_keywords", tmdb_id)
        keywords = []
        for k in getattr(res, "keywords", []):
            # Support both AsObj (runtime) and dict (tests)
            name = getattr(k, "name", None)
            if not name and isinstance(k, dict):
                name = k.get("name")
            if name:
                keywords.append(name)
        return keywords
    except Exception as e:
        kodilog(f"Error fetching movie keywords for {tmdb_id}: {e}")
        return []


def filter_anime_by_keyword(results, mode):
    filtered_anime = []
    anime_api = TmdbAnime()
    list_lock = threading.Lock()

    def check_and_append(item, filtered_anime, list_lock):
        keywords_data = anime_api.tmdb_keywords(mode, item["id"])
        keywords = keywords_data["results"] if mode == "tv" else keywords_data["keywords"]
        for keyword in keywords:
            if keyword["id"] == 210024:
                with list_lock:
                    filtered_anime.append(item)
                break

    execute_thread_pool(results["results"], check_and_append, filtered_anime, list_lock)
    results["results"] = filtered_anime
    results["total_results"] = len(filtered_anime)
    return results


def _build_trailer_ids(ids, title=None, title_key="title"):
    trailer_ids = dict(ids or {})
    if title and not trailer_ids.get(title_key):
        trailer_ids[title_key] = title
    return trailer_ids


def build_play_trailer_context_menu_item(ids, media_type, title=None, title_key="title"):
    trailer_ids = _build_trailer_ids(ids=ids, title=title, title_key=title_key)
    if not any(trailer_ids.get(key) for key in ("trailer_yt_id", "trailer_url", "tmdb_id")):
        return None

    params = {
        "name": "play_trailer",
        "media_type": media_type,
        "tmdb_id": trailer_ids.get("tmdb_id"),
        "title": trailer_ids.get(title_key),
    }
    if trailer_ids.get("trailer_yt_id"):
        params["yt_id"] = trailer_ids.get("trailer_yt_id")
    if trailer_ids.get("trailer_url"):
        params["youtube_url"] = trailer_ids.get("trailer_url")

    return (
        translation(90672),
        kodi_play_media(**params),
    )


def add_tmdb_movie_context_menu(mode, media_type, title=None, ids=None):
    if ids is None:
        ids = {}
    context_menu = [
        (
            translation(90049),
            kodi_play_media(
                name="rescrape_tmdb_media",
                mode=mode,
                media_type=media_type,
                query=title,
                tmdb_id=ids.get("tmdb_id"),
                rescrape=True,
            ),
        ),
        (
            translation(90115),
            kodi_play_media(
                name="rescrape_tmdb_media",
                mode=mode,
                media_type=media_type,
                query=title,
                tmdb_id=ids.get("tmdb_id"),
                force_select=True,
            ),
        ),
        (
            translation(90755),
            "RunPlugin(plugin://plugin.video.jacktook/?action=source_manager_toggle)",
        ),
        (
            "Extras",
            f"RunPlugin(plugin://plugin.video.jacktook/?action=extras&id={ids.get('tmdb_id')}&imdb_id={ids.get('imdb_id', '')}&media_type={media_type}&title={parse.quote(title or '')})",
        ),
        (
            translation(90733),
            action_url_run(
                "tmdb_search_modes",
                mode=mode,
                media_type=media_type,
                query=title,
                tmdb_id=ids.get("tmdb_id"),
            ),
        ),
    ]
    trailer_item = build_play_trailer_context_menu_item(
        ids=ids,
        media_type="tv" if media_type == "tv" else "movie",
        title=title,
    )
    if trailer_item:
        context_menu.append(trailer_item)
    context_menu += [
        (
            translation(90050),
            container_update(
                name="search_tmdb_recommendations",
                mode=mode,
                ids=ids,
                media_type=media_type,
            ),
        ),
        (
            translation(90051),
            container_update(
                name="search_tmdb_similar",
                mode=mode,
                ids=ids,
                media_type=media_type,
            ),
        ),
        (
            translation(90081),
            container_update(
                name="search_people_by_id",
                mode=mode,
                ids=ids,
                media_type=media_type,
            ),
        ),
        (
            translation(90205),
            kodi_play_media(
                name="add_to_library",
                data=json.dumps(
                    {
                        "title": title,
                        "ids": ids,
                        "mode": media_type,
                        "timestamp": datetime.now().strftime("%a, %d %b %Y %I:%M %p"),
                    }
                ),
            ),
        ),
        (
            translation(90205),
            kodi_play_media(
                name="add_to_library",
                data=json.dumps(
                    {
                        "title": ids.get("name") or ids.get("title"),
                        "ids": ids,
                        "mode": mode,
                        "timestamp": datetime.now().strftime("%a, %d %b %Y %I:%M %p"),
                    }
                ),
            ),
        ),
        (
            translation(90116),
            container_update(name="settings"),
        ),
    ]
    return context_menu


def add_tmdb_show_context_menu(mode, ids=None, title=None):
    if ids is None:
        ids = {}
    context_menu = [
        (
            "Extras",
            f"RunPlugin(plugin://plugin.video.jacktook/?action=extras&id={ids.get('tmdb_id')}&imdb_id={ids.get('imdb_id', '')}&media_type=tv&title={parse.quote(title or ids.get('name') or ids.get('title') or '')})",
        ),
    ]
    trailer_item = build_play_trailer_context_menu_item(
        ids=ids,
        media_type="tv",
        title=title or ids.get("name") or ids.get("title"),
        title_key="name",
    )
    if trailer_item:
        context_menu.append(trailer_item)
    context_menu += [
        (
            translation(90050),
            container_update(
                name="search_tmdb_recommendations",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90051),
            container_update(
                name="search_tmdb_similar",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90081),
            container_update(
                name="search_people_by_id",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90205),
            kodi_play_media(
                name="add_to_library",
                data=json.dumps(
                    {
                        "title": ids.get("name") or ids.get("title"),
                        "ids": ids,
                        "mode": mode,
                        "timestamp": datetime.now().strftime("%a, %d %b %Y %I:%M %p"),
                    }
                ),
            ),
        ),
        (
            translation(90755),
            "RunPlugin(plugin://plugin.video.jacktook/?action=source_manager_toggle)",
        ),
        (
            translation(90116),
            container_update(name="settings"),
        ),
    ]
    return context_menu


def add_tmdb_episode_context_menu(mode, tv_name=None, tv_data=None, ids=None):
    if ids is None:
        ids = {}
    return [
        (
            translation(90049),
            kodi_play_media(
                name="search",
                mode=mode,
                query=tv_name,
                ids=ids,
                tv_data=tv_data,
                rescrape=True,
            ),
        ),
        (
            translation(90115),
            kodi_play_media(
                name="search",
                mode=mode,
                query=tv_name,
                ids=ids,
                tv_data=tv_data,
                force_select=True,
            ),
        ),
        (
            translation(90755),
            "RunPlugin(plugin://plugin.video.jacktook/?action=source_manager_toggle)",
        ),
        (
            translation(90733),
            action_url_run(
                "tmdb_episode_search_modes",
                mode=mode,
                media_type="tv",
                query=tv_name,
                ids=ids,
                tv_data=tv_data,
            ),
        ),
        (
            translation(90050),
            container_update(
                name="search_tmdb_recommendations",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90051),
            container_update(
                name="search_tmdb_similar",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90081),
            container_update(
                name="search_people_by_id",
                mode=mode,
                ids=ids,
            ),
        ),
        (
            translation(90116),
            container_update(name="settings"),
        ),
    ]


def get_excluded_languages():
    raw = get_setting("excluded_languages", "[]")
    try:
        return json.loads(raw) if raw else []
    except (ValueError, TypeError):
        return []


def filter_excluded_languages(results, force_allow_lang=None):
    excluded = get_excluded_languages()
    if not excluded:
        return results

    input_count = len(results)
    filtered = []
    excluded_counts = {}

    for r in results:
        if isinstance(r, dict):
            lang = r.get("original_language")
        else:
            lang = getattr(r, "original_language", None)

        if lang == force_allow_lang or lang not in excluded:
            filtered.append(r)
        else:
            excluded_counts[lang] = excluded_counts.get(lang, 0) + 1

    output_count = len(filtered)
    if input_count != output_count:
        kodilog(
            f"Excluded language filter: {input_count} -> {output_count} "
            f"(excluded={excluded}, force_allow={force_allow_lang!r}, "
            f"removed_by_lang={excluded_counts})",
            level=xbmc.LOGINFO,
        )

    return filtered
