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

//! This module contains definition of [`FunctionAction`] type.

use std::fmt::Debug;

use anyhow::Error;
use textpieces_foundations::{Parameter, ParameterValue};

use crate::parameters::ParametersTuple;

/// Function type used for function-based actions.
pub type ActionFn<const N_PARAMS: usize, P> = fn(&str, P) -> Result<String, Error>;

/// Function-based action definition structure.
#[derive(Clone, Copy, derive_more::Debug)]
pub struct FunctionAction<const N_PARAMS: usize, P: ParametersTuple<N_PARAMS>> {
    /// Action ID.
    pub id: &'static str,
    /// Action name.
    pub name: &'static str,
    /// Action description.
    pub description: &'static str,
    /// Labels of action parameters.
    pub parameter_labels: [&'static str; N_PARAMS],
    /// Action function.
    #[debug("<function>")]
    pub function: ActionFn<N_PARAMS, P>,
}

/// Sealed trait for generic function action.
pub trait GenericFunctionAction: Send + Sync + Debug {
    /// Returns action ID.
    fn id(&self) -> &'static str;

    /// Returns action name.
    fn name(&self) -> &'static str;

    /// Returns action description.
    fn description(&self) -> &'static str;

    /// Returns list of action parametes.
    fn parameters(&self) -> Vec<Parameter>;

    /// Calls inner action function with given parameters.
    ///
    /// Requires from caller to validate parameters first.
    fn call_function(&self, input: &str, params: &[ParameterValue]) -> Result<String, Error>;
}

impl<const N_PARAMS: usize, P: ParametersTuple<N_PARAMS>> GenericFunctionAction
    for FunctionAction<N_PARAMS, P>
{
    fn id(&self) -> &'static str {
        self.id
    }

    fn name(&self) -> &'static str {
        self.name
    }

    fn description(&self) -> &'static str {
        self.description
    }

    fn parameters(&self) -> Vec<Parameter> {
        self.parameter_labels
            .into_iter()
            .zip(P::types_())
            .map(|(label, type_)| Parameter {
                type_,
                label: label.to_owned(),
            })
            .collect()
    }

    fn call_function(&self, input: &str, params: &[ParameterValue]) -> Result<String, Error> {
        (self.function)(input, P::from_parameters(params))
    }
}

/// Static reference to generic function action.
pub type DynFunctionAction = &'static dyn GenericFunctionAction;
