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

//! This module contains high-level API for action performing.

use thiserror::Error;

use crate::{actions::Actions, ActionProvider, ParameterType, ParameterValue};

/// Error type for action performing.
#[derive(Debug, Error)]
pub enum ActionError {
    /// The action is not found.
    #[error("the action is not found")]
    NotFound,

    /// Wrong number of parameters passed.
    #[error(
        "{expected} parameter{} expected, {found} found",
        if .expected == &1 { " is" } else {"s are"})
    ]
    WrongParamNumber {
        /// Number of passed parameters.
        expected: usize,
        /// Expected number of parameters.
        found: usize,
    },

    /// Wrong parameter type.
    #[error("parameter {index} has wrong type: {expected:?} expected, {found:?} found")]
    WrongParamType {
        /// Parameter index.
        index: usize,
        /// Expected parameter type.
        expected: ParameterType,
        /// Found parameter type.
        found: ParameterType,
    },

    /// Provider-specific error.
    #[error("{0}")]
    Failed(String),
}

impl<P: ActionProvider> Actions<P> {
    /// Performs an action.
    ///
    /// # Errors
    /// - [`ActionError::NotFound`] if there is no action
    ///   with such ID.
    /// - [`ActionError::WrongParamNumber`] if number of
    ///   parameters passed to the action is invalid.
    /// - [`ActionError::WrongParamType`] if type of a
    ///   parameter is invalid.
    /// - [`ActionError::Failed`] if the action fails
    ///   with some error.
    pub async fn perform_action(
        &self,
        action_id: &str,
        parameters: &[ParameterValue],
        input: &str,
    ) -> Result<String, ActionError> {
        // Check if the action exists
        let action = self.actions.get(action_id).ok_or(ActionError::NotFound)?;

        // Validate number of passed parameters
        if action.parameters.len() != parameters.len() {
            return Err(ActionError::WrongParamNumber {
                expected: action.parameters.len(),
                found: parameters.len(),
            });
        }

        // Validate parameter types
        let wrongly_typed_parameter = action
            .parameters
            .iter()
            .zip(parameters)
            .enumerate()
            .find(|(_, (param, param_value))| param.type_ != param_value.type_());

        if let Some((index, (param, param_value))) = wrongly_typed_parameter {
            return Err(ActionError::WrongParamType {
                index,
                expected: param.type_,
                found: param_value.type_(),
            });
        }

        self.provider
            .perform_action(action_id, parameters, input)
            .await
            .map_err(ActionError::Failed)
    }
}
