pub(crate) mod channel;
pub(crate) mod music_artist;
pub(crate) mod music_charts;
pub(crate) mod music_details;
pub(crate) mod music_genres;
pub(crate) mod music_item;
pub(crate) mod music_new;
pub(crate) mod music_playlist;
pub(crate) mod music_search;
pub(crate) mod player;
pub(crate) mod playlist;
pub(crate) mod search;
pub(crate) mod trends;
pub(crate) mod url_endpoint;
pub(crate) mod video_details;
pub(crate) mod video_item;

pub(crate) use channel::Channel;
pub(crate) use music_artist::MusicArtist;
pub(crate) use music_artist::MusicArtistAlbums;
pub(crate) use music_charts::MusicCharts;
pub(crate) use music_details::MusicDetails;
pub(crate) use music_details::MusicLyrics;
pub(crate) use music_details::MusicRelated;
pub(crate) use music_genres::MusicGenre;
pub(crate) use music_genres::MusicGenres;
pub(crate) use music_item::MusicContinuation;
pub(crate) use music_new::MusicNew;
pub(crate) use music_playlist::MusicPlaylist;
pub(crate) use music_search::MusicSearch;
pub(crate) use music_search::MusicSearchSuggestion;
pub(crate) use player::Player;
pub(crate) use playlist::Playlist;
pub(crate) use playlist::PlaylistCont;
pub(crate) use search::Search;
pub(crate) use search::SearchSuggestion;
pub(crate) use trends::Startpage;
pub(crate) use trends::Trending;
pub(crate) use url_endpoint::ResolvedUrl;
pub(crate) use video_details::VideoComments;
pub(crate) use video_details::VideoDetails;
pub(crate) use video_item::YouTubeListItem;
pub(crate) use video_item::YouTubeListMapper;

#[cfg(feature = "rss")]
pub(crate) mod channel_rss;
#[cfg(feature = "rss")]
pub(crate) use channel_rss::ChannelRss;

use std::borrow::Cow;
use std::marker::PhantomData;

use serde::{
    de::{IgnoredAny, Visitor},
    Deserialize,
};
use serde_with::{json::JsonString, serde_as, VecSkipError};

use crate::error::ExtractionError;
use crate::serializer::{text::Text, MapResult, VecSkipErrorWrap};

use self::video_item::YouTubeListRenderer;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContentRenderer<T> {
    pub content: T,
}

#[derive(Debug)]
pub(crate) struct ContentsRenderer<T> {
    pub contents: Vec<T>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct ContentsRendererLogged<T> {
    #[serde(alias = "items")]
    pub contents: MapResult<Vec<T>>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Tab<T> {
    pub tab_renderer: ContentRenderer<T>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SectionList<T> {
    pub section_list_renderer: ContentsRenderer<T>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TwoColumnBrowseResults<T> {
    pub two_column_browse_results_renderer: ContentsRenderer<T>,
}

#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ThumbnailsWrap {
    #[serde(default)]
    pub thumbnail: Thumbnails,
}

/// List of images in different resolutions.
/// Not only used for thumbnails, but also for avatars and banners.
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Thumbnails {
    #[serde(default)]
    pub thumbnails: Vec<Thumbnail>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Thumbnail {
    pub url: String,
    pub width: u32,
    pub height: u32,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationItemRenderer {
    pub continuation_endpoint: ContinuationEndpoint,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationEndpoint {
    pub continuation_command: ContinuationCommand,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationCommand {
    pub token: String,
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Icon {
    pub icon_type: IconType,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum IconType {
    /// Checkmark for verified channels
    Check,
    /// Music note for verified artists
    OfficialArtistBadge,
    /// Like button
    Like,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChannelBadge {
    pub metadata_badge_renderer: ChannelBadgeRenderer,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChannelBadgeRenderer {
    pub style: ChannelBadgeStyle,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum ChannelBadgeStyle {
    BadgeStyleTypeVerified,
    BadgeStyleTypeVerifiedArtist,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Alert {
    pub alert_renderer: AlertRenderer,
}

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AlertRenderer {
    #[serde_as(as = "Text")]
    pub text: String,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ResponseContext {
    pub visitor_data: Option<String>,
}

// CONTINUATION

#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Continuation {
    /// Number of search results
    #[serde_as(as = "Option<JsonString>")]
    pub estimated_results: Option<u64>,
    #[serde(
        alias = "onResponseReceivedCommands",
        alias = "onResponseReceivedEndpoints"
    )]
    #[serde_as(as = "Option<VecSkipError<_>>")]
    pub on_response_received_actions: Option<Vec<ContinuationActionWrap>>,
    /// Used for channel video rich grid renderer
    ///
    /// A/B test seen on 19.10.2022
    pub continuation_contents: Option<RichGridContinuationContents>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationActionWrap {
    #[serde(alias = "reloadContinuationItemsCommand")]
    pub append_continuation_items_action: ContinuationAction,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationAction {
    pub continuation_items: MapResult<Vec<YouTubeListItem>>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RichGridContinuationContents {
    pub rich_grid_continuation: YouTubeListRenderer,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MusicContinuationData {
    #[serde(alias = "nextRadioContinuationData")]
    pub next_continuation_data: MusicContinuationDataInner,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MusicContinuationDataInner {
    pub continuation: String,
}

// ERROR

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ErrorResponse {
    pub error: ErrorResponseContent,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ErrorResponseContent {
    pub message: String,
}

// DESERIALIZER

impl<'de, T> Deserialize<'de> for ContentsRenderer<T>
where
    T: Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct ItemVisitor<T>(PhantomData<T>);

        impl<'de, T> Visitor<'de> for ItemVisitor<T>
        where
            T: Deserialize<'de>,
        {
            type Value = ContentsRenderer<T>;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("map")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                let mut contents = None;

                while let Some(k) = map.next_key::<Cow<'de, str>>()? {
                    if k == "contents" || k == "tabs" || k == "items" {
                        contents = Some(ContentsRenderer {
                            contents: map.next_value::<VecSkipErrorWrap<T>>()?.0,
                        });
                    } else {
                        map.next_value::<IgnoredAny>()?;
                    }
                }

                contents.ok_or(serde::de::Error::missing_field("contents"))
            }
        }

        deserializer.deserialize_map(ItemVisitor(PhantomData::<T>))
    }
}

// MAPPING

impl From<Thumbnail> for crate::model::Thumbnail {
    fn from(tn: Thumbnail) -> Self {
        crate::model::Thumbnail {
            url: tn.url,
            width: tn.width,
            height: tn.height,
        }
    }
}

impl From<Thumbnails> for Vec<crate::model::Thumbnail> {
    fn from(ts: Thumbnails) -> Self {
        ts.thumbnails
            .into_iter()
            .map(|t| crate::model::Thumbnail {
                url: t.url,
                width: t.width,
                height: t.height,
            })
            .collect()
    }
}

impl From<Vec<ChannelBadge>> for crate::model::Verification {
    fn from(badges: Vec<ChannelBadge>) -> Self {
        badges.get(0).map_or(crate::model::Verification::None, |b| {
            match b.metadata_badge_renderer.style {
                ChannelBadgeStyle::BadgeStyleTypeVerified => Self::Verified,
                ChannelBadgeStyle::BadgeStyleTypeVerifiedArtist => Self::Artist,
            }
        })
    }
}

impl From<Icon> for crate::model::Verification {
    fn from(icon: Icon) -> Self {
        match icon.icon_type {
            IconType::Check => Self::Verified,
            IconType::OfficialArtistBadge => Self::Artist,
            _ => Self::None,
        }
    }
}

pub(crate) fn alerts_to_err(id: &str, alerts: Option<Vec<Alert>>) -> ExtractionError {
    ExtractionError::NotFound {
        id: id.to_owned(),
        msg: alerts
            .map(|alerts| {
                alerts
                    .into_iter()
                    .map(|a| a.alert_renderer.text)
                    .collect::<Vec<_>>()
                    .join(" ")
                    .into()
            })
            .unwrap_or_default(),
    }
}
