use std::convert::Infallible;

use chrono::{DateTime, Utc};
use lunar_lib::{
    database::Id,
    formatter::{FormatTable, Taggable},
    paths::sys::sanitize_str,
};
use serde::{Deserialize, Serialize};

use crate::library::{
    album::Album,
    artist::Artist,
    image_art::ImageArt,
    track::{Track, lyric_data::LyricData},
};

/// Track metadata. Defines metadata to be read from and stored on files
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct TrackMeta {
    /// The album this track belongs to
    ///
    /// # Database Safety
    ///
    /// When changing this value:
    /// - The old album should have its reference to this track removed
    /// - The new album needs to have a pointer to this track added
    pub album: Option<Id<Album>>,

    /// The artists this track belong to
    ///
    /// # Database Safety
    ///
    /// When changing this value:
    /// - The remove artists should have its reference to this track removed
    /// - The added artists need to have a pointer to this track added
    pub artists: Vec<Id<Artist>>,
    pub date: Option<DateTime<Utc>>,
    pub genre: Vec<String>,
    pub lyric_data: Option<LyricData>,
    pub art: Option<Id<ImageArt>>,
    pub other: Vec<(String, String)>,
    pub title: Option<String>,
}

impl TrackMeta {
    #[must_use]
    pub fn new() -> Self {
        Self {
            title: None,
            artists: Vec::new(),
            album: None,

            date: None,
            genre: Vec::new(),
            art: None,

            lyric_data: None,

            other: Vec::new(),
        }
    }

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

    #[must_use]
    pub fn album(&self) -> Option<Id<Album>> {
        self.album
    }

    #[must_use]
    pub fn safe_title(&self) -> &str {
        self.title.as_deref().unwrap_or(Track::UNKNOWN)
    }
}

impl Taggable for TrackMeta {
    type Err = Infallible;

    fn fill_table(&self, table: &mut FormatTable) -> Result<(), Self::Err> {
        if let Some(value) = self.date {
            table.add_entry("date", value.to_string());
        }
        if let Some(value) = &self.title {
            table.add_entry("title", sanitize_str(value));
        }
        if !self.genre.is_empty() {
            table.add_entry("genre", sanitize_str(self.genre.join(";")));
        }

        Ok(())
    }
}
