use bitflags::bitflags;
use bon::Builder;
use serde_repr::{Deserialize_repr, Serialize_repr};
use zeroize::Zeroizing;

use crate::{
    gateway::{
        intents::GatewayEventFlags, payload::outgoing::PresenceUpdateOutgoing, shard::ShardInfo,
    },
    misc::serde_bitflags,
};

#[derive(Serialize_repr, Deserialize_repr, Copy, Clone, Debug)]
#[repr(u8)]
pub enum ActivityType {
    Playing = 0,
    Streaming,
    Listening,
    Watching,
    Custom,
    Competing,
}

#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug)]
pub struct Timestamps {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end: Option<u64>,
}

#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
#[repr(u8)]
pub enum StatusDisplayType {
    Name = 0,
    State,
    Details,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct ActivityParty {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    /// `(current_size, max_size)`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub size: Option<(i32, i32)>,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct ActivityAssets {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub large_image: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub large_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub large_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub small_image: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub small_text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub small_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub invite_cover_image: Option<String>,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct ActivitySecrets {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub join: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spectate: Option<String>,
    #[serde(rename = "match", skip_serializing_if = "Option::is_none")]
    pub r#match: Option<String>,
}

bitflags! {
    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    pub struct ActivityFlags: u64 {
        const INSTANCE = 1 << 0;
        const JOIN = 1 << 1;
        const SPECTATE = 1 << 2;
        const JOIN_REQUEST = 1 << 3;
        const SYNC = 1 << 4;
        const PLAY = 1 << 5;
        const PARTY_PRIVACY_FRIENDS = 1 << 6;
        const PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7;
        const EMBEDDED = 1 << 8;
    }
}

serde_bitflags!(ActivityFlags, u64);

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ActivityButton {
    pub label: String,
    pub url: String,
}

// Activities don't exist yet.
/*
/// Bot users are only able to set `name`, `state`, `type`, and `url`.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Builder)]
pub struct Activity {
    #[builder(into)]
    pub name: String,
    #[serde(rename = "type")]
    pub r#type: ActivityType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[builder(default = {
        #[expect(clippy::cast_possible_truncation)]
        let value = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as u64;
        value
    })]
    pub created_at: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timestamps: Option<Timestamps>,
    /// Application ID for the game.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub application_id: Option<Id<ApplicationMarker>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status_display_type: Option<StatusDisplayType>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub emoji: Option<Emoji>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub party: Option<ActivityParty>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub assets: Option<ActivityAssets>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub secrets: Option<ActivitySecrets>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instance: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub flags: Option<ActivityFlags>,
    /// Max. 2 buttons.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub buttons: Option<Vec<ActivityButton>>,
}
*/

// TODO: This has more (optional) fields when logging in as a user
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Builder)]
pub struct ConnectionProperties {
    /// The operating system, e.g. "linux".
    #[builder(into)]
    pub os: String,
    /// The library name.
    #[builder(into)]
    pub browser: String,
    /// The library name.
    #[builder(into)]
    pub device: String,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Builder)]
pub struct Identify {
    #[builder(into)]
    pub token: Zeroizing<String>,
    pub properties: ConnectionProperties,
    // Doesn't appear to exist - https://github.com/fluxerapp/fluxer/blob/5da26d4ed5ef9f3fe8bef993c0f10ea4f4ee9c1d/fluxer_gateway/src/gateway/gateway_handler.erl#L466
    // pub compress: bool,
    // /// Value between 50 and 250.
    // #[serde(skip_serializing_if = "Option::is_none")]
    // pub large_threshold: Option<u8>,
    // Also doesn't appear to exist, but I'll be keeping this for the future.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub shard: Option<ShardInfo>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub presence: Option<PresenceUpdateOutgoing>,
    // TODO: Check whether this is actually `Intents`...
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ignored_events: Option<GatewayEventFlags>,
    // TODO: There might be a "flags" field, have to check...
}
