use std::ops::{Deref, DerefMut};

use bitflags::bitflags;
use bon::Builder;
use serde::{Deserialize, Serialize};

use crate::{
    misc::{HexColor, serde_bitflags},
    time::{
        duration::{Duration, Seconds},
        timestamp::{Timestamp, representations::Iso8601},
    },
};

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct MessageEmbed {
    #[serde(flatten)]
    pub base: MessageEmbedBase,
    /// Internal nested embeds generated by unfurlers.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub children: Option<Vec<EmbedChild>>,
}

impl Deref for MessageEmbed {
    type Target = MessageEmbedBase;
    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl DerefMut for MessageEmbed {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

bitflags! {
    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    pub struct EmbedMediaFlags: u32 {
        const CONTAINS_EXPLICIT_MEDIA = 1 << 4;
        const IS_ANIMATED = 1 << 5;
    }
}

serde_bitflags! {EmbedMediaFlags, u32}

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct EmbedMedia {
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_hash: Option<String>,
    /// The MIME type of the media.
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_type: Option<String>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration: Option<Duration<Seconds>>,
    pub flags: EmbedMediaFlags,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<u32>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub placeholder: Option<String>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub proxy_url: Option<String>,
    #[builder(into)]
    pub url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<u32>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct EmbedAuthor {
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon_url: Option<String>,
    #[builder(into)]
    pub name: String,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub proxy_icon_url: Option<String>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct EmbedField {
    #[builder(default = true)]
    pub inline: bool,
    #[builder(into)]
    pub name: String,
    #[builder(into)]
    pub value: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct EmbedChild {
    #[serde(flatten)]
    pub base: MessageEmbedBase,
}

impl Deref for EmbedChild {
    type Target = MessageEmbedBase;
    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl DerefMut for EmbedChild {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct EmbedFooter {
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon_url: Option<String>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub proxy_icon_url: Option<String>,
    #[builder(into)]
    pub text: String,
}

#[expect(clippy::doc_markdown)]
#[derive(Serialize, Deserialize, Clone, Debug, Builder, PartialEq, Eq)]
pub struct MessageEmbedBase {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub audio: Option<EmbedMedia>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub author: Option<EmbedAuthor>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub color: Option<HexColor>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fields: Option<Vec<EmbedField>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub footer: Option<EmbedFooter>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image: Option<EmbedMedia>,
    #[builder(default = false)]
    #[serde(default)]
    pub nsfw: bool,
    /// The provider (e.g. YouTube, Twitter).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider: Option<EmbedAuthor>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thumbnail: Option<EmbedMedia>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamp: Option<Timestamp<Iso8601>>,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// The type of embed (e.g., rich, image, video, gifv, article, link).
    #[builder(into, default = "rich".to_owned())]
    #[serde(rename = "type")]
    pub r#type: String,
    #[builder(into)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub video: Option<EmbedMedia>,
}
