// SPDX-FileCopyrightText: 2023 Gleb Smirnov <glebsmirnov0708@gmail.com>
//
// SPDX-License-Identifier: MIT

//! This module contains high-level API for actions modifying.

use std::collections::hash_map::Entry;

use thiserror::Error;

use crate::{action::Action, actions::Actions, provider::ActionProviderMut};

/// Error type for actions modifying.
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum ModifyStorageError<P: ActionProviderMut> {
    /// Failed to remove or update an action
    /// since it isn't presented in the storage.
    #[error("the action is not presented")]
    NotPresented,

    /// Failed to add an action to the storage
    /// since it's already presented in it.
    #[error("the action is already presented")]
    AlreadyPresented,

    /// Provider-specific error.
    #[error(transparent)]
    Failed(P::SaveActionsError),
}

/// Common result type for modifying operations.
type Result<P> = std::result::Result<(), ModifyStorageError<P>>;

impl<P: ActionProviderMut> Actions<P> {
    /// Adds new action.
    ///
    /// Note that this method only adds metadata for
    /// the action. Action provider can require some
    /// extra work to make new action work.
    ///
    /// # Errors
    /// - [`ModifyStorageError::AlreadyPresented`] if an action with this ID
    ///   already exists.
    /// - [`ModifyStorageError::Failed`] if the action provider fails to save
    ///   modification.
    pub async fn add(&mut self, id: &str, action: Action) -> Result<P> {
        let Entry::Vacant(entry) = self.actions.entry(id.to_owned()) else {
            return Err(ModifyStorageError::AlreadyPresented);
        };

        entry.insert(action);

        let result = self.save().await;
        if result.is_err() {
            self.actions.remove(id);
        }

        result
    }

    /// Removes an action.
    ///
    /// # Errors
    /// - [`ModifyStorageError::NotPresented`] if there is no action with this ID.
    /// - [`ModifyStorageError::Failed`] if the action provider fails to save
    ///   modification.
    pub async fn remove(&mut self, id: &str) -> Result<P> {
        let action = self
            .actions
            .remove(id)
            .ok_or(ModifyStorageError::NotPresented)?;

        let result = self.save().await;
        if result.is_err() {
            self.actions.insert(id.to_owned(), action);
        }

        result
    }

    /// Replaces the action with given ID with new one.
    ///
    /// # Errors
    /// - [`ModifyStorageError::NotPresented`] if there is no action with this ID.
    /// - [`ModifyStorageError::Failed`] if the action provider fails to save
    ///   modification.
    pub async fn update(&mut self, id: &str, action: Action) -> Result<P> {
        let Entry::Occupied(mut entry) = self.actions.entry(id.to_owned()) else {
            return Err(ModifyStorageError::NotPresented);
        };

        let old_action = entry.insert(action);

        let result = self.save().await;
        if result.is_err() {
            self.actions.insert(id.to_owned(), old_action);
        }

        result
    }

    /// Save modified actions.
    async fn save(&self) -> Result<P> {
        self.provider
            .update_actions(&self.actions)
            .await
            .map_err(ModifyStorageError::Failed)
    }
}
