use_prelude!();

use std::hash::Hash;

use ffi_sdk::{DynFfiPresenceCallback, FfiPresenceObserver};
use uuid::Uuid;

use crate::error::Result;

trait_alias! {
    pub trait PresenceCallback = FnMut(&PresenceGraph) + Send + Sync + 'static
}

/// Use [`ditto.presence().register_observer(...)`] to create a new `PresenceObserver`.
///
/// # Example
///
/// ```
/// use dittolive_ditto::Ditto;
/// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
///
/// // To register a new presence observer
/// let observer = ditto.presence().register_observer(|graph| {
///     println!("Graph updated: {:?}", graph);
/// })?;
///
/// // Later, to cancel the observer
/// observer.cancel();
/// # Ok(())
/// # }
/// ```
///
/// [`ditto.presence().register_observer(...)`]: crate::presence::Presence::register_observer
pub struct PresenceObserver {
    pub(crate) handle: repr_c::Box<FfiPresenceObserver>,
}

impl PresenceObserver {
    /// Create a new `PresenceObserver` with the given callback to receive presence updates.
    pub(crate) fn new<F>(ditto: &ffi_sdk::BoxedDitto, mut observer: F) -> Result<PresenceObserver>
    where
        F: PresenceCallback,
    {
        let ffi_callback: repr_c::Box<DynFfiPresenceCallback> =
            Box::new(move |graph: c_slice::Box<u8>, _: Blocking| {
                let graph =
                    serde_json::from_slice(&graph).expect("should deserialize presence graph");
                observer(&graph);
            })
            .into();

        let handle = ffi_sdk::dittoffi_presence_register_observer_throws(ditto, ffi_callback)
            .into_rust_result()?;
        Ok(Self { handle })
    }

    /// Cancels this [`PresenceObserver`]. The registered callback will no longer be called.
    pub fn cancel(&self) {
        ffi_sdk::dittoffi_presence_observer_cancel(&self.handle);
    }

    /// Returns whether this [`PresenceObserver`] has been cancelled.
    pub fn is_cancelled(&self) -> bool {
        ffi_sdk::dittoffi_presence_observer_is_cancelled(&self.handle)
    }

    /// Returns the unique identifier for this [`PresenceObserver`].
    fn id(&self) -> Uuid {
        let buffer = ffi_sdk::dittoffi_presence_observer_id(&self.handle);
        Uuid::from_slice(buffer.as_slice()).expect("bug: expected valid UUID")
    }
}

impl Eq for PresenceObserver {}
impl PartialEq for PresenceObserver {
    fn eq(&self, other: &Self) -> bool {
        self.id() == other.id()
    }
}

impl Hash for PresenceObserver {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id().hash(state);
    }
}
