use std::sync::Arc;

use lunar_lib::database::{DatabaseEntry, EntryIdIteratorExt, TransactionError};

use crate::{
    database::LibraryDb,
    library::artist::{Artist, ArtistId},
};

pub trait ArtistGroup {
    fn artist_ids(&self) -> &[ArtistId];

    fn artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, TransactionError>;

    fn artists_cache(&self, db: &LibraryDb) -> Result<Vec<Arc<Artist>>, TransactionError>;

    fn main_artist_id(&self) -> Option<ArtistId>;

    fn main_artist(&self, db: &LibraryDb) -> Result<Option<Artist>, TransactionError>;

    fn featuring_artist_ids(&self) -> &[ArtistId];

    fn featuring_artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, TransactionError>;

    fn add_artist(&mut self, artist: ArtistId);

    fn add_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>);

    fn remove_artist(&mut self, artist: ArtistId);

    fn remove_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>);
}

impl ArtistGroup for Vec<ArtistId> {
    fn artist_ids(&self) -> &[ArtistId] {
        self
    }

    fn artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, TransactionError> {
        self.iter().copied().db_get_batch(db)
    }

    fn artists_cache(&self, db: &LibraryDb) -> Result<Vec<Arc<Artist>>, TransactionError> {
        self.iter().copied().cache_get_batch(db)
    }

    fn main_artist_id(&self) -> Option<ArtistId> {
        self.first().copied()
    }

    fn main_artist(&self, db: &LibraryDb) -> Result<Option<Artist>, TransactionError> {
        if let Some(first) = self.first() {
            let artist = Artist::db_get(*first, db)?
                .expect("ArtistGroup main artist did not exist in the database");
            Ok(Some(artist))
        } else {
            Ok(None)
        }
    }

    fn featuring_artist_ids(&self) -> &[ArtistId] {
        self.get(1..).unwrap_or_default()
    }

    fn featuring_artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, TransactionError> {
        self.featuring_artist_ids().iter().copied().db_get_batch(db)
    }

    fn add_artist(&mut self, artist: ArtistId) {
        if !self.contains(&artist) {
            self.push(artist);
        }
    }

    fn add_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>) {
        artists.into_iter().for_each(|artist| {
            self.add_artist(artist);
        });
    }

    fn remove_artist(&mut self, artist: ArtistId) {
        self.retain(|a| *a != artist);
    }

    fn remove_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>) {
        let artists: Vec<ArtistId> = artists.into_iter().collect();
        self.retain(|a| !artists.contains(a));
    }
}
