import typing
from collections.abc import Callable
from functools import wraps
from gettext import GNUTranslations
from typing import ParamSpec, TypeVar

from ytmusicapi.navigation import *
from ytmusicapi.type_alias import JsonDict, JsonList

from .constants import DOT_SEPARATOR_RUN

P = ParamSpec("P")
R = TypeVar("R")

if typing.TYPE_CHECKING:
    pass


def parse_menu_playlists(data: JsonDict, result: JsonDict) -> None:
    """performs in-place replacement based on :param:data: in :param:result"""
    menu_items = nav(data, MENU_ITEMS, True)
    if menu_items is None:
        return
    watch_menu = find_objects_by_key(menu_items, MNIR)
    for item in [_x[MNIR] for _x in watch_menu]:
        icon = nav(item, ICON_TYPE)
        if icon == "MUSIC_SHUFFLE":
            watch_key = "shuffleId"
        elif icon == "MIX":
            watch_key = "radioId"
        else:
            continue

        watch_id = nav(item, ["navigationEndpoint", "watchPlaylistEndpoint", "playlistId"], True)
        if not watch_id:
            watch_id = nav(item, ["navigationEndpoint", "watchEndpoint", "playlistId"], True)
        if watch_id:
            result[watch_key] = watch_id


def get_item_text(item: JsonDict, index: int, run_index: int = 0, none_if_absent: bool = False) -> str | None:
    column = get_flex_column_item(item, index)
    if not column:
        return None
    if none_if_absent and len(column["text"]["runs"]) < run_index + 1:
        return None

    return typing.cast(str, column["text"]["runs"][run_index]["text"])


def get_flex_column_item(item: JsonDict, index: int) -> JsonDict | None:
    if (
        len(item["flexColumns"]) <= index
        or "text" not in item["flexColumns"][index]["musicResponsiveListItemFlexColumnRenderer"]
        or "runs" not in item["flexColumns"][index]["musicResponsiveListItemFlexColumnRenderer"]["text"]
    ):
        return None

    return typing.cast(JsonDict, item["flexColumns"][index]["musicResponsiveListItemFlexColumnRenderer"])


def get_fixed_column_item(item: JsonDict, index: int) -> JsonDict | None:
    if (
        "text" not in item["fixedColumns"][index]["musicResponsiveListItemFixedColumnRenderer"]
        or "runs" not in item["fixedColumns"][index]["musicResponsiveListItemFixedColumnRenderer"]["text"]
    ):
        return None

    return typing.cast(JsonDict, item["fixedColumns"][index]["musicResponsiveListItemFixedColumnRenderer"])


def get_dot_separator_index(runs: JsonList) -> int:
    try:
        index = runs.index(DOT_SEPARATOR_RUN)
    except ValueError:
        index = len(runs)

    return index


def parse_duration(duration: str | None) -> int | None:
    """
    Parse duration to a value in seconds.

    :param duration: Duration string
    :return: Duration in seconds
    """
    # duration may be falsy or a single space: ' '
    if not duration or not duration.strip():
        return None
    duration_split = duration.strip().split(":")
    for d in duration_split:
        if not d.isdigit():  # For e.g: "2,343"
            return None
    mapped_increments = zip([1, 60, 3600], reversed(duration_split))
    seconds = sum(multiplier * int(time) for multiplier, time in mapped_increments)
    return seconds


class SupportsLang(typing.Protocol):
    lang: GNUTranslations


def i18n(method: Callable[P, R]) -> Callable[P, R]:
    @wraps(method)
    def _impl(*args: P.args, **kwargs: P.kwargs) -> R:
        self = args[0]
        method.__globals__["_"] = self.lang.gettext  # type: ignore
        return method(*args, **kwargs)

    return _impl


def parse_id_name(sub_run: JsonDict | None) -> JsonDict:
    return {
        "id": nav(sub_run, NAVIGATION_BROWSE_ID, True),
        "name": nav(sub_run, ["text"], True),
    }
