pub mod config;

use std::{collections::HashMap, fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResultsFull {
    pub tracks: Option<SearchSection<SearchTrackHit>>,
    pub uploads: Option<SearchSection<SearchTrackHit>>,
    pub albums: Option<SearchSection<SearchAlbumHit>>,
    pub playlists: Option<SearchSection<SearchPlaylistHit>>,
    pub videos: Option<SearchSection<SearchVideoHit>>,
    pub artists: Option<SearchSection<SearchArtistHit>>,
    pub genres: Option<Vec<SearchGenre>>,
    #[serde(rename = "contentTypeFilters")]
    pub content_type_filters: Option<Vec<String>>,
    #[serde(rename = "topHits")]
    pub top_hits: Option<Vec<SearchTopHit>>,
    #[serde(rename = "queryId")]
    pub query_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchGenre {
    pub api_path: String,
    pub title: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchSection<T> {
    pub items: Vec<T>,
    pub total_number_of_items: u32,
    pub cacheable: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchTrackHit {
    pub artifact_type: String,
    pub id: u64,
    pub editable: bool,
    pub title: String,
    pub album: SearchAlbumSummary,
    pub artists: Vec<SearchArtistSummary>,
    pub version: Option<String>,
    pub duration: u64,
    pub popularity: u32,
    pub double_popularity: f64,
    pub track_number: Option<u32>,
    pub volume_number: Option<u32>,
    pub explicit: bool,
    pub replay_gain: f64,
    pub audio_quality: Option<String>,
    #[serde(rename = "allowStreaming")]
    pub allow_streaming: Option<bool>,
    #[serde(rename = "streamStartDate")]
    pub stream_start_date: Option<String>,
    pub stream_ready: Option<bool>,
    pub audio_modes: Option<Vec<String>>,
    pub mixes: Option<HashMap<String, String>>,
    pub ad_supported_stream_ready: Option<bool>,
    pub media_metadata: Option<crate::client::models::media::MediaMetadata>,
    pub provider_name: Option<String>,
    pub dj_ready: Option<bool>,
    pub stem_ready: Option<bool>,
    pub pay_to_stream: Option<bool>,
    pub genres: Option<Vec<String>>,
    pub audio_analysis_attributes: Option<Value>,
    pub upload: Option<bool>,
    pub access_type: Option<String>,
    pub spotlighted: Option<bool>,
    pub created_at: Option<String>,
    pub user_id: Option<u64>,
    pub isrc: Option<String>,
    pub peak: Option<f64>,
    pub pre_paywall_presentation: Option<String>,
    pub copyright: Option<String>,
    pub first_available: Option<String>,
    pub linked_stereo_isrc: Option<String>,
    pub premium_streaming_only: Option<bool>,
    pub url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchAlbumSummary {
    pub id: u64,
    pub title: String,
    pub version: Option<String>,
    pub cover: String,
    pub vibrant_color: Option<String>,
    pub video_cover: Option<String>,
    pub release_date: Option<String>,
    pub upload: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchArtistSummary {
    pub id: Option<u64>,
    pub name: Option<String>,
    pub handle: Option<String>,
    pub picture: Option<String>,
    pub user_id: Option<u64>,
    #[serde(rename = "type")]
    pub artist_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchAlbumHit {
    pub artifact_type: String,
    pub id: u64,
    pub title: String,
    pub artists: Vec<SearchArtistSummary>,
    pub duration: Option<u64>,
    pub cover: Option<String>,
    pub vibrant_color: Option<String>,
    pub video_cover: Option<String>,
    pub copyright: Option<String>,
    pub number_of_volumes: Option<u32>,
    pub number_of_tracks: Option<u32>,
    pub number_of_videos: Option<u32>,
    pub popularity: Option<u32>,
    pub double_popularity: Option<f64>,
    pub version: Option<String>,
    pub release_date: Option<String>,
    #[serde(rename = "type")]
    pub album_type: Option<String>,
    pub explicit: Option<bool>,
    pub upc: Option<String>,
    pub audio_quality: Option<String>,
    pub master_album: Option<SearchMasterAlbum>,
    pub allow_streaming: Option<bool>,
    pub stream_start_date: Option<String>,
    pub stream_ready: Option<bool>,
    pub pay_to_stream: Option<bool>,
    pub audio_modes: Option<Vec<String>>,
    pub ad_supported_stream_ready: Option<bool>,
    pub media_metadata: Option<crate::client::models::media::MediaMetadata>,
    pub provider_name: Option<String>,
    pub dj_ready: Option<bool>,
    pub stem_ready: Option<bool>,
    pub upload: Option<bool>,
    pub access_type: Option<String>,
    pub created_at: Option<String>,
    pub user_id: Option<u64>,
    pub premium_streaming_only: Option<bool>,
    pub url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchMasterAlbum {
    pub id: String,
    pub release_date: Option<String>,
    pub categories: Option<Vec<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchPlaylistHit {
    pub artifact_type: String,
    pub uuid: String,
    pub title: String,
    pub number_of_tracks: Option<u32>,
    pub number_of_audio_tracks: Option<u32>,
    pub number_of_video_tracks: Option<u32>,
    pub description: Option<String>,
    pub duration: Option<u64>,
    pub last_updated: Option<String>,
    pub created: Option<String>,
    pub image: Option<String>,
    pub square_image: Option<String>,
    pub user_id: Option<u64>,
    pub created_by_artists: Option<Vec<SearchArtistSummary>>,
    pub popularity: Option<u32>,
    pub double_popularity: Option<f64>,
    pub public_playlist: Option<bool>,
    pub promoted_artists: Option<Vec<SearchArtistSummary>>,
    pub last_item_added_at: Option<String>,
    pub url: Option<String>,
    pub creator: Option<Value>,
    #[serde(rename = "type")]
    pub playlist_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchVideoHit {
    pub artifact_type: String,
    pub id: u64,
    pub audio_only_track_id: Option<u64>,
    pub duration: u64,
    pub ads_url: Option<String>,
    pub ads_pre_paywall_only: Option<bool>,
    pub title: String,
    pub artists: Vec<SearchArtistSummary>,
    pub album: Option<SearchAlbumSummary>,
    pub version: Option<String>,
    #[serde(rename = "type")]
    pub video_type: Option<String>,
    pub volume_number: Option<u32>,
    pub track_number: Option<u32>,
    pub isrc: Option<String>,
    pub copyright: Option<String>,
    pub explicit: bool,
    pub release_date: Option<String>,
    pub image: Option<String>,
    pub vibrant_color: Option<String>,
    pub popularity: Option<u32>,
    pub double_popularity: Option<f64>,
    pub allow_streaming: Option<bool>,
    pub stream_start_date: Option<String>,
    pub stream_ready: Option<bool>,
    pub ad_supported_stream_ready: Option<bool>,
    pub provider_id: Option<u64>,
    pub provider_name: Option<String>,
    pub dj_ready: Option<bool>,
    pub stem_ready: Option<bool>,
    pub created_at: Option<String>,
    pub quality: Option<String>,
    pub image_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchArtistHit {
    pub artifact_type: String,
    pub id: u64,
    pub name: String,
    pub picture: Option<String>,
    pub popularity: Option<u32>,
    pub double_popularity: Option<f64>,
    pub artist_types: Option<Vec<String>>,
    pub artist_roles: Option<Vec<SearchArtistRole>>,
    pub mixes: Option<HashMap<String, String>>,
    pub vibrant_color: Option<String>,
    pub selected_album_cover_fallback: Option<String>,
    pub handle: Option<String>,
    pub user_id: Option<u64>,
    pub artwork_id: Option<String>,
    pub spotlighted: Option<bool>,
    pub contributions_enabled: Option<bool>,
    pub cash_app_onboarded: Option<bool>,
    pub url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchArtistRole {
    pub category_id: i64,
    pub category: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchTopHit {
    pub value: Value,
    #[serde(rename = "type")]
    pub hit_type: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchSuggestionsFull {
    pub history: Vec<SearchSuggestionEntry>,
    pub suggestions: Vec<SearchSuggestionEntry>,
    #[serde(rename = "directHits")]
    pub direct_hits: Vec<SearchSuggestionDirectHit>,
    #[serde(rename = "suggestionUuid")]
    pub suggestion_uuid: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchSuggestionEntry {
    pub query: String,
    pub highlights: Vec<SearchHighlight>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchHighlight {
    pub start: u32,
    pub length: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchSuggestionDirectHit {
    pub value: Value,
    #[serde(rename = "type")]
    pub hit_type: String,
}

#[derive(Clone, Debug)]
pub enum SearchType {
    Albums,
    Artists,
    Playlists,
    TopHits,
    Tracks,
    Videos,
}

impl FromStr for SearchType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "albums" => Ok(SearchType::Albums),
            "artists" => Ok(SearchType::Artists),
            "playlists" => Ok(SearchType::Playlists),
            "topHits" | "tophits" | "top_hits" => Ok(SearchType::TopHits),
            "tracks" => Ok(SearchType::Tracks),
            "videos" => Ok(SearchType::Videos),
            _ => Err(format!("unknown search type: {s}")),
        }
    }
}

impl Display for SearchType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            SearchType::Albums => "albums",
            SearchType::Artists => "artists",
            SearchType::Playlists => "playlists",
            SearchType::TopHits => "topHits",
            SearchType::Tracks => "tracks",
            SearchType::Videos => "videos",
        };
        write!(f, "{}", s)
    }
}
