use std::collections::HashMap;

use serde::{Deserialize, Deserializer};

#[derive(Debug, Deserialize)]
pub struct InfoJson {
    pub id: String,
    pub title: String,
    pub formats: Vec<Format>,
    pub thumbnails: Vec<ThumbNail>,
    pub thumbnail: String,
    pub description: String,
    pub channel_id: String,
    pub channel_url: String,
    pub duration: u32,
    pub view_count: u32,
    pub age_limit: u32,
    pub webpage_url: String,
    pub categories: Vec<String>,
    pub tags: Vec<String>,
    pub playable_in_embed: bool,
    pub live_status: String,
    _format_sort_fields: Vec<String>,
    pub automatic_captions: HashMap<String, Vec<Caption>>,
    pub subtitles: Subtitles,
    pub comment_count: u32,
    pub like_count: u32,
    pub channel: String,
    pub channel_follower_count: u32,
    pub channel_is_verified: bool,
    pub uploader: String,
    pub uploader_id: String,
    pub uploader_url: String,
    pub upload_date: String,
    pub availability: String,
    pub webpage_url_basename: String,
    pub webpage_url_domain: String,
    pub extractor: String,
    pub extractor_key: String,
    pub display_id: String,
    pub fulltitle: String,
    pub duration_string: String,
    pub is_live: bool,
    pub was_live: bool,
    pub epoch: u32,
    pub comments: Vec<Comment>,
    pub sponsorblock_chapters: Vec<SponsorblockChapter>,
    pub format: String,
    pub format_id: String,
    pub ext: String,
    pub protocol: String,
    pub language: Option<String>,
    pub format_note: String,
    pub filesize_approx: u32,
    pub tbr: f64,
    pub width: u32,
    pub height: u32,
    pub resolution: String,
    pub fps: f64,
    pub dynamic_range: String,
    pub vcodec: String,
    pub vbr: f64,
    pub aspect_ratio: f64,
    pub acodec: String,
    pub abr: f64,
    pub asr: u32,
    pub audio_channels: u32,
    _type: String,
    _version: Version,
}

#[derive(Debug, Deserialize)]
pub struct Subtitles {}

#[derive(Debug, Deserialize)]
pub struct Version {
    pub version: String,
    pub release_git_head: String,
    pub repository: String,
}

#[derive(Debug, Deserialize)]
pub struct SponsorblockChapter {}

#[derive(Debug, Deserialize, Clone)]
#[serde(from = "String")]
pub enum Parent {
    Root,
    Id(String),
}

impl Parent {
    pub fn id(&self) -> Option<&str> {
        if let Self::Id(id) = self {
            Some(&id)
        } else {
            None
        }
    }
}

impl From<String> for Parent {
    fn from(value: String) -> Self {
        if value == "root" {
            Self::Root
        } else {
            Self::Id(value)
        }
    }
}

#[derive(Debug, Deserialize, Clone)]
#[serde(from = "String")]
pub struct Id {
    pub id: String,
}
impl From<String> for Id {
    fn from(value: String) -> Self {
        Self {
            // Take the last element if the string is split with dots, otherwise take the full id
            id: value.split('.').last().unwrap_or(&value).to_owned(),
        }
    }
}

#[derive(Debug, Deserialize, Clone)]
pub struct Comment {
    pub id: Id,
    pub text: String,
    #[serde(default = "zero")]
    pub like_count: u32,
    pub author_id: String,
    pub author: String,
    pub author_thumbnail: String,
    pub parent: Parent,
    #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
    pub edited: bool,
    // Can't also be deserialized, as it's already used in 'edited'
    // _time_text: String,
    pub timestamp: i64,
    pub author_url: String,
    pub author_is_uploader: bool,
    pub is_favorited: bool,
}
fn zero() -> u32 {
    0
}
fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(d)?;
    if s.contains(" (edited)") {
        Ok(true)
    } else {
        Ok(false)
    }
}

#[derive(Debug, Deserialize)]
pub struct Caption {
    pub ext: String,
    pub url: String,
    pub name: String,
}

#[derive(Debug, Deserialize)]
pub struct ThumbNail {
    pub url: String,
    pub preference: i32,
    pub id: String,
    pub height: Option<u32>,
    pub width: Option<u32>,
    pub resolution: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct Format {
    pub format_id: String,
    pub format_note: Option<String>,
    pub ext: String,
    pub protocol: String,
    pub acodec: Option<String>,
    pub vcodec: String,
    pub url: String,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub fps: Option<f64>,
    pub rows: Option<u32>,
    pub columns: Option<u32>,
    pub fragments: Option<Vec<Fragment>>,
    pub resolution: String,
    pub aspect_ratio: Option<f64>,
    pub http_headers: HttpHeader,
    pub audio_ext: String,
    pub video_ext: String,
    pub vbr: Option<f64>,
    pub abr: Option<f64>,
    pub format: String,
}

#[derive(Debug, Deserialize)]
pub struct HttpHeader {
    #[serde(alias = "User-Agent")]
    pub user_agent: String,
    #[serde(alias = "Accept")]
    pub accept: String,
    #[serde(alias = "Accept-Language")]
    pub accept_language: String,
    #[serde(alias = "Sec-Fetch-Mode")]
    pub sec_fetch_mode: String,
}

#[derive(Debug, Deserialize)]
pub struct Fragment {
    pub url: String,
    pub duration: f64,
}