import re

from ytmusicapi.continuations import *
from ytmusicapi.helpers import sum_total_duration
from ytmusicapi.models.content.enums import VoteStatus
from ytmusicapi.type_alias import JsonDict, JsonList, ParseFuncType, RequestFuncBodyType

from ..helpers import to_int
from .songs import *


def parse_playlist_header(response: JsonDict) -> JsonDict:
    playlist: JsonDict = {}
    editable_header = nav(response, [*HEADER, *EDITABLE_PLAYLIST_DETAIL_HEADER], True)
    playlist["owned"] = editable_header is not None
    playlist["privacy"] = "PUBLIC"
    if editable_header is not None:  # owned playlist
        header = nav(editable_header, HEADER_DETAIL)
        playlist["privacy"] = editable_header["editHeader"]["musicPlaylistEditHeaderRenderer"]["privacy"]
    else:
        header = nav(response, HEADER_DETAIL, True)
        if header is None:
            header = nav(
                response, [*TWO_COLUMN_RENDERER, *TAB_CONTENT, *SECTION_LIST_ITEM, *RESPONSIVE_HEADER]
            )

    playlist.update(parse_playlist_header_meta(header))
    if playlist["thumbnails"] is None:
        playlist["thumbnails"] = nav(header, THUMBNAIL_CROPPED, True)
    playlist["description"] = nav(header, ["description", *DESCRIPTION_SHELF, *DESCRIPTION], True)
    playlist["year"] = nav(header, SUBTITLE2)

    return playlist


def parse_playlist_header_meta(header: JsonDict) -> JsonDict:
    playlist_meta = {
        "views": None,
        "duration": None,
        "trackCount": None,
        "title": "".join([run["text"] for run in header.get("title", {}).get("runs", [])]),
        "thumbnails": nav(header, THUMBNAILS),
    }
    if "facepile" in header:
        avatar_renderer = nav(header, ["facepile", "avatarStackViewModel", "rendererContext"])
        avatar_command = nav(
            avatar_renderer,
            [
                "commandContext",
                "onTap",
                "innertubeCommand",
            ],
            True,
        )

        if (
            nav(avatar_command, ["showEngagementPanelEndpoint", "identifier", "tag"], True)
            == "PAplaylist_collaborate"
        ):
            avatars = nav(header, ["facepile", "avatarStackViewModel", "avatars"])

            playlist_meta["collaborators"] = {
                "text": nav(avatar_renderer, ["accessibilityContext", "label"]),
                "avatars": [avatar["avatarViewModel"]["image"]["sources"][0] for avatar in avatars],
            }
        else:
            playlist_meta["author"] = {
                "name": nav(header, ["facepile", "avatarStackViewModel", "text", "content"]),
                "id": nav(
                    avatar_command,
                    [
                        "browseEndpoint",
                        "browseId",
                    ],
                    True,
                ),
            }
    if "runs" in header["secondSubtitle"]:
        second_subtitle_runs = header["secondSubtitle"]["runs"]
        has_views = (len(second_subtitle_runs) > 3) * 2
        playlist_meta["views"] = None if not has_views else to_int(second_subtitle_runs[0]["text"])
        has_duration = (len(second_subtitle_runs) > 1) * 2
        playlist_meta["duration"] = (
            None if not has_duration else second_subtitle_runs[has_views + has_duration]["text"]
        )
        song_count_text = second_subtitle_runs[has_views + 0]["text"]
        song_count_search = re.findall(r"\d+", song_count_text)
        # extract the digits from the text, return 0 if no match
        playlist_meta["trackCount"] = (
            to_int("".join(song_count_search)) if song_count_search is not None else None
        )

    return playlist_meta


def parse_audio_playlist(
    response: JsonDict, limit: int | None, request_func: RequestFuncBodyType
) -> JsonDict:
    playlist: JsonDict = {
        "owned": False,
        "privacy": "PUBLIC",
        "description": None,
        "views": None,
        "duration": None,
        "tracks": [],
        "thumbnails": [],
        "related": [],
    }

    section_list = nav(response, [*TWO_COLUMN_RENDERER, "secondaryContents", *SECTION])
    content_data = nav(section_list, [*CONTENT, "musicPlaylistShelfRenderer"])

    playlist["id"] = nav(content_data, ["targetId"])

    playlist["tracks"] = []
    if "contents" in content_data:
        playlist["tracks"] = parse_playlist_items(content_data["contents"])

        parse_func: ParseFuncType = lambda contents: parse_playlist_items(contents)
        playlist["tracks"].extend(get_continuations_2025(content_data, limit, request_func, parse_func))

    playlist["trackCount"] = len(playlist["tracks"])

    playlist["title"] = playlist["tracks"][0]["album"]["name"]

    playlist["duration_seconds"] = sum_total_duration(playlist)
    return playlist


def parse_playlist_items(
    results: JsonList,
    is_album: bool = False,
    is_collaborative: bool = False,
) -> JsonList:
    songs = []
    for result in results:
        if MRLIR not in result:
            continue
        data = result[MRLIR]
        song = parse_playlist_item(data, is_album, is_collaborative)
        if song:
            songs.append(song)

    return songs


def parse_playlist_item(
    data: JsonDict,
    is_album: bool = False,
    is_collaborative: bool = False,
) -> JsonDict | None:
    videoId = setVideoId = creditsBrowseId = None
    like = None

    # if the item has a menu, find its setVideoId
    if "menu" in data:
        for item in nav(data, MENU_ITEMS):
            if "menuServiceItemRenderer" in item:
                menu_service = nav(item, MENU_SERVICE)
                if "playlistEditEndpoint" in menu_service:
                    setVideoId = nav(menu_service, ["playlistEditEndpoint", "actions", 0, "setVideoId"], True)
                    videoId = nav(
                        menu_service, ["playlistEditEndpoint", "actions", 0, "removedVideoId"], True
                    )
            elif MNIR in item:
                maybe_credits_browse_id = nav(item, [MNIR, *NAVIGATION_BROWSE_ID], True)
                if maybe_credits_browse_id and maybe_credits_browse_id.startswith("MPTC"):
                    creditsBrowseId = maybe_credits_browse_id

    song_menu_data = {"inLibrary": None, "pinnedToListenAgain": None} | parse_song_menu_data(data)

    # if item is not playable, the videoId was retrieved above
    if nav(data, PLAY_BUTTON, none_if_absent=True) is not None:
        if "playNavigationEndpoint" in nav(data, PLAY_BUTTON):
            videoId = nav(data, PLAY_BUTTON)["playNavigationEndpoint"]["watchEndpoint"]["videoId"]

            if "menu" in data:
                like = nav(data, MENU_LIKE_STATUS, True)

    isAvailable = True
    if "musicItemRendererDisplayPolicy" in data:
        isAvailable = data["musicItemRendererDisplayPolicy"] != "MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT"

    # For unavailable items and for album track lists indexes are preset,
    # because meaning of the flex column cannot be reliably found using navigationEndpoint
    use_preset_columns = True if isAvailable is False or is_album is True else None

    title_index = 0 if use_preset_columns else None
    artist_index = 1 if use_preset_columns else None
    duration_index = None
    # collaborative playlists have duration in flexColumns (between artist and album)
    album_index = 3 if is_collaborative else 2 if use_preset_columns else None
    user_channel_indexes = []
    unrecognized_index = None

    for index in range(len(data["flexColumns"])):
        flex_column_item = get_flex_column_item(data, index)
        navigation_endpoint = nav(flex_column_item, [*TEXT_RUN, "navigationEndpoint"], True)

        if not navigation_endpoint:
            run = nav(flex_column_item, TEXT_RUN, True)
            if run and "text" in run:
                parsed = parse_song_run(run)
                if parsed["type"] == "duration":
                    duration_index = index
                else:
                    unrecognized_index = index if unrecognized_index is None else unrecognized_index

            continue

        if "watchEndpoint" in navigation_endpoint:
            title_index = index
        elif "browseEndpoint" in navigation_endpoint:
            page_type = nav(
                navigation_endpoint,
                [
                    "browseEndpoint",
                    "browseEndpointContextSupportedConfigs",
                    "browseEndpointContextMusicConfig",
                    "pageType",
                ],
            )

            # MUSIC_PAGE_TYPE_ARTIST for regular songs, MUSIC_PAGE_TYPE_UNKNOWN for uploads
            if page_type == "MUSIC_PAGE_TYPE_ARTIST" or page_type == "MUSIC_PAGE_TYPE_UNKNOWN":
                artist_index = index
            elif page_type in ["MUSIC_PAGE_TYPE_ALBUM", "MUSIC_PAGE_TYPE_AUDIOBOOK"]:
                album_index = index
            elif page_type == "MUSIC_PAGE_TYPE_USER_CHANNEL":
                user_channel_indexes.append(index)
            # Non music videos, for example: podcast episodes
            elif page_type == "MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE":
                title_index = index

    # Extra check for rare songs, where artist is non-clickable and does not have navigationEndpoint
    if artist_index is None and unrecognized_index is not None:
        artist_index = unrecognized_index

    # Extra check for non-song videos, last channel is treated as artist
    if artist_index is None and user_channel_indexes:
        artist_index = user_channel_indexes[-1]

    title = get_item_text(data, title_index) if title_index is not None else None
    if title == "Song deleted":
        return None

    artists = parse_song_artists(data, artist_index) if artist_index is not None else None

    album = parse_song_album(data, album_index) if album_index is not None else None

    views = get_item_text(data, 2) if is_album else None

    duration = get_item_text(data, duration_index) if duration_index else None
    if "fixedColumns" in data:
        if "simpleText" in nav(get_fixed_column_item(data, 0), ["text"]):
            duration = nav(get_fixed_column_item(data, 0), ["text", "simpleText"])
        else:
            duration = nav(get_fixed_column_item(data, 0), TEXT_RUN_TEXT)

    thumbnails = nav(data, THUMBNAILS, True)

    isExplicit = nav(data, BADGE_LABEL, True) is not None

    videoType = nav(
        data,
        [*MENU_ITEMS, 0, MNIR, "navigationEndpoint", *NAVIGATION_VIDEO_TYPE],
        True,
    )

    voting_status = nav(data, ENGAGEMENT_BAR, none_if_absent=True)

    community_vote_status = (
        None
        if voting_status is None
        else {
            "netVoteValue": voting_status["votes"],
            "status": VoteStatus(voting_status["status"]),
        }
    )

    song = {
        "videoId": videoId,
        "title": title,
        "artists": artists,
        "album": album,
        "likeStatus": like,
        **(song_menu_data),
        "thumbnails": thumbnails,
        "isAvailable": isAvailable,
        "isExplicit": isExplicit,
        "videoType": videoType,
        "views": views,
        "communityVoteStatus": community_vote_status,
    }

    if is_album:
        song["trackNumber"] = int(nav(data, ["index", "runs", 0, "text"])) if isAvailable else None

    if duration:
        song["duration"] = duration
        song["duration_seconds"] = parse_duration(duration)
    if setVideoId:
        song["setVideoId"] = setVideoId
    if creditsBrowseId:
        song["creditsBrowseId"] = creditsBrowseId

    return song


def validate_playlist_id(playlistId: str) -> str:
    return playlistId if not playlistId.startswith("VL") else playlistId[2:]
