use chrono::{DateTime, Utc};
use lunar_lib::database::Id;
use serde::{Deserialize, Serialize};

use crate::library::{artist::Artist, image_art::ImageArt, track::Track};

pub mod database_impls;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Album {
    pub title: String,
    pub art: Option<Id<ImageArt>>,

    /// The tracks belonging to this album
    ///
    /// # Database Safety
    ///
    /// When changing this value:
    /// - The removed tracks should have its reference to this album removed
    /// - The added tracks need to have a pointer to this album added
    /// - It should be sorted by `disc_num` ascending, then `track_num` ascending. [`None`] should be treated as [`u32::MAX`]
    pub tracks: Vec<TrackReference>,

    /// The artists this album belong to
    ///
    /// # Database Safety
    ///
    /// When changing this value:
    /// - The remove artists should have its reference to this album removed
    /// - The added artists need to have a pointer to this album added
    pub artists: Vec<Id<Artist>>,
    pub disc_total: Option<u32>,
    pub genre: Vec<String>,
    pub track_total: Option<u32>,
    pub date: Option<DateTime<Utc>>,
}

impl Album {
    pub const UNKNOWN: &str = "UNKNOWN ALBUM";
}

// Core
impl Album {
    pub(crate) fn new(name: String, artists: Vec<Id<Artist>>, tracks: Vec<TrackReference>) -> Self {
        Self {
            artists,
            art: None,
            disc_total: None,
            genre: Vec::new(),
            title: name,
            track_total: None,
            date: None,
            tracks,
        }
    }
}

// Accessors
impl Album {
    #[must_use]
    pub fn name(&self) -> &str {
        &self.title
    }

    #[must_use]
    pub fn artists(&self) -> &[Id<Artist>] {
        &self.artists
    }

    #[must_use]
    pub fn track_refs(&self) -> &[TrackReference] {
        &self.tracks
    }

    #[must_use]
    pub fn tracks(&self) -> impl Iterator<Item = Id<Track>> {
        self.tracks.iter().map(|i| i.id)
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
pub struct TrackReference {
    pub id: Id<Track>,
    pub track_num: Option<u32>,
    pub disc_num: Option<u32>,
}

impl std::ops::Deref for TrackReference {
    type Target = Id<Track>;

    fn deref(&self) -> &Self::Target {
        &self.id
    }
}

impl PartialEq for TrackReference {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl Eq for TrackReference {}
