use lunar_lib::database::{
    CompareAndSwapTransaction, Createable, CustomTransactionError, DatabaseEntry, TransactionError,
    caching::Cacheable,
};

use crate::{
    database::{COLLECTION_CACHE, LibraryDb},
    library::{
        collectable::Collectable,
        collection::{
            Collection, CollectionCreationError, CollectionId, CollectionType,
            DynamicCollectionRules,
        },
    },
};

impl DatabaseEntry for Collection {
    type Id = CollectionId;
    type Db = LibraryDb;

    const VERSION_NUMBER: u32 = 1;
    const TREE_NAME: &str = "collection";

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

    fn read_only(&self) -> bool {
        self.read_only
    }
}

impl Cacheable for Collection {
    fn cache() -> &'static std::sync::Mutex<lunar_lib::database::caching::DbCache<Self>> {
        &COLLECTION_CACHE
    }
}

#[derive(Debug, Clone)]
pub struct CollectionCreateArgs {
    name: String,
    collection_type: CollectionType,
}

impl CollectionCreateArgs {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            collection_type: CollectionType::Static {
                collectables: Vec::new(),
            },
        }
    }

    #[must_use]
    pub fn with_items(mut self, items: Vec<Collectable>) -> Self {
        self.collection_type = CollectionType::Static {
            collectables: items,
        };
        self
    }

    #[must_use]
    pub fn with_rules(mut self, rules: Vec<DynamicCollectionRules>) -> Self {
        self.collection_type = CollectionType::Dynamic { rules };
        self
    }
}

impl Createable for Collection {
    type CreateArgs = CollectionCreateArgs;
    type Err = CollectionCreationError;

    fn tx_create(
        cas_tx: &mut CompareAndSwapTransaction<Self::Db>,
        args: Self::CreateArgs,
    ) -> Result<Self, CustomTransactionError<Self::Err>> {
        let mut collection =
            Collection::new_static(args.name).map_err(CustomTransactionError::Closure)?;

        if cas_tx.tx_get(collection.id())?.is_some() {
            return Err(TransactionError::AlreadyInDatabase.into());
        }

        collection.items = args.collection_type;

        Ok(collection)
    }
}
