use teloxide::{
    dispatching::DpHandlerDescription,
    types::{Message, CallbackQuery},
};
use teloxide::dptree::{self, Handler};
use teloxide::dptree::di::DependencyMap;
use serde::de::DeserializeOwned;
use crate::HandlerSettings;

/// Extension methods for working with `dptree` handlers.
pub trait FilterNotCommandExt<Output> {
    /// Returns a handler that filters out any command (messages starting with `/`).
    ///
    /// ## Dependency requirements
    ///
    ///  - [`crate::types::Message`]
    #[must_use]
    fn filter_not_command(self) -> Self;

    /// Returns a handler that filters callback queries by whether their data
    /// can be deserialized to type `C`.
    ///
    /// ## Dependency requirements
    ///
    ///  - [`crate::types::CallbackQuery`]
    #[must_use]
    fn filter_callback_data<C>(self) -> Self
    where
        C: DeserializeOwned + Send + Sync + 'static;
}

impl<Output> FilterNotCommandExt<Output> for Handler<'static, Output, DpHandlerDescription>
where
    Output: Send + Sync + 'static,
{
    fn filter_not_command(self) -> Self {
        self.chain(filter_not_command::<Output>())
    }

    fn filter_callback_data<C>(self) -> Self
    where
        C: DeserializeOwned + Send + Sync + 'static,
    {
        self.chain(filter_callback_data::<C, Output>())
    }
}

/// Returns a handler that filters out any command (messages starting with `/`).
///
/// A call to this function is the same as `dptree::entry().filter_not_command()`.
///
/// See [`HandlerExt::filter_not_command`].
///
/// ## Dependency requirements
///
///  - [`crate::types::Message`]
#[must_use]
pub fn filter_not_command<Output>() -> Handler<'static, Output, DpHandlerDescription>
where
    Output: Send + Sync + 'static,
{
    dptree::filter(|message: Message| {
        message.text().or_else(|| message.caption())
            .map(|text| !text.trim_start().starts_with('/'))
            .unwrap_or(true)
    })
}

/// Returns a handler that filters callback queries by whether their data
/// can be deserialized to type `C`.
///
/// A call to this function is the same as `dptree::entry().filter_callback_data()`.
///
/// See [`FilterNotCommandExt::filter_callback_data`].
///
/// ## Dependency requirements
///
///  - [`crate::types::CallbackQuery`]
#[must_use]
pub fn filter_callback_data<C, Output>() -> Handler<'static, Output, DpHandlerDescription>
where
    C: DeserializeOwned + Send + Sync + 'static,
    Output: Send + Sync + 'static,
{
    dptree::filter(|q: CallbackQuery| {
        if let Some(data) = q.data {
            serde_json::from_str::<C>(&data).is_ok()
        } else {
            false
        }
    })
}

/// Returns a handler that filters messages by whether their text matches
/// a localized string.
///
/// A call to this macro is the same as `dptree::entry().filter_localized_text!()`.
///
/// See [`FilterNotCommandExt::filter_localized_text`].
///
/// ## Dependency requirements
///
///  - `String` (message text)
///  - [`HandlerSettings`]
#[macro_export]
macro_rules! filter_localized_text {
    ($base_language:expr, $key:literal) => {
        dptree::filter(move |msg_text: String /* "👤 Profile 👤" */, settings: HandlerSettings| {
            let text = t8x!($base_language, $key, &settings.language, "menu-button");
            log::debug!("filter_localized_text: msg_text: {}, text: {}", msg_text, text);
            msg_text == text
        })
    };
}
