Documentation
use std::sync::Arc;

use thiserror::Error;
use tokio::sync::mpsc::{error::SendError, Sender};
use uuid::Uuid;

use crate::service::{BoxedError, PinnedBoxedFutureResult};

pub enum Callback<T>
where
    T: Send + Sync + 'static,
{
    Channel(Sender<Arc<T>>),
    Closure(Box<dyn Fn(Arc<T>) -> Result<(), BoxedError> + Send + Sync>),
    AsyncClosure(Box<dyn Fn(Arc<T>) -> PinnedBoxedFutureResult<()> + Send + Sync>),
}

#[derive(Debug, Error)]
pub enum DispatchError<T>
where
    T: Send + Sync + 'static,
{
    #[error("Failed to send data to channel: {0}")]
    ChannelSend(#[from] SendError<Arc<T>>),

    #[error("Failed to dispatch data to closure: {0}")]
    Closure(BoxedError),

    #[error("Failed to dispatch data to async closure: {0}")]
    AsyncClosure(BoxedError),
}

pub struct Subscriber<T>
where
    T: Send + Sync + 'static,
{
    pub name: String,
    pub log_on_error: bool,
    pub remove_on_error: bool,
    pub callback: Callback<T>,

    pub uuid: Uuid,
}

impl<T> Subscriber<T>
where
    T: Send + Sync + 'static,
{
    pub fn new<S>(name: S, log_on_error: bool, remove_on_error: bool, callback: Callback<T>) -> Self
    where
        S: Into<String>,
    {
        Self {
            name: name.into(),
            log_on_error,
            remove_on_error,
            callback,
            uuid: Uuid::new_v4(),
        }
    }

    pub async fn dispatch(&self, data: Arc<T>) -> Result<(), DispatchError<T>> {
        match &self.callback {
            Callback::Channel(sender) => {
                sender.send(data).await.map_err(DispatchError::ChannelSend)
            }
            Callback::Closure(closure) => closure(data).map_err(DispatchError::Closure),
            Callback::AsyncClosure(closure) => {
                closure(data).await.map_err(DispatchError::AsyncClosure)
            }
        }
    }
}

impl<T> PartialEq for Subscriber<T>
where
    T: Send + Sync + 'static,
{
    fn eq(&self, other: &Self) -> bool {
        self.uuid == other.uuid
    }
}

impl<T> Eq for Subscriber<T> where T: Send + Sync {}