//! Transport trait — the SDK never opens a socket. Hosts implement this against their backend.

use crate::{
    conversation::ConversationId, device::DeviceId, identity::UserId, message::MessageEnvelope,
    sync::SyncCursor, Result,
};
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

// See `storage.rs` — same reason: `JsFuture` is `!Send` on wasm32. The native bound is kept so
// UniFFI's tokio multi-thread runtime can schedule the future across worker threads.
#[cfg(not(target_arch = "wasm32"))]
pub type TransportFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
pub type TransportFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + 'a>>;

/// Server returns events strictly after the given cursor, sorted by (epoch ASC, server_seq ASC).
/// May return at-most-`limit` envelopes; caller pages.
pub trait Transport: Send + Sync + Debug {
    /// Publish a single envelope. Servers may batch; callers should not depend on it.
    fn send<'a>(&'a self, envelope: MessageEnvelope) -> TransportFuture<'a, ()>;

    /// Pull events for one conversation since `cursor`. Empty list = caught up.
    fn fetch_since<'a>(
        &'a self,
        conversation_id: ConversationId,
        cursor: SyncCursor,
        limit: u32,
    ) -> TransportFuture<'a, Vec<MessageEnvelope>>;

    /// Subscribe to live events. Returned subscription's drop cancels.
    fn subscribe<'a>(
        &'a self,
        callback: Arc<dyn Fn(MessageEnvelope) + Send + Sync>,
    ) -> TransportFuture<'a, TransportSubscription>;

    /// Look up published KeyPackages for a user's currently-active devices.
    fn discover_devices<'a>(
        &'a self,
        user_id: UserId,
    ) -> TransportFuture<'a, Vec<DiscoveredDevice>>;
}

#[derive(Debug, Clone)]
pub struct DiscoveredDevice {
    pub device_id: DeviceId,
    pub key_package: Vec<u8>, // serialized OpenMLS KeyPackage
}

/// Cancels its subscription on drop.
pub struct TransportSubscription {
    pub(crate) cancel: Box<dyn FnOnce() + Send>,
}

impl std::fmt::Debug for TransportSubscription {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("TransportSubscription")
    }
}

impl TransportSubscription {
    pub fn new(cancel: impl FnOnce() + Send + 'static) -> Self {
        Self {
            cancel: Box::new(cancel),
        }
    }
}

impl Drop for TransportSubscription {
    fn drop(&mut self) {
        // Replace with a no-op closure so we can take ownership and call once.
        let cancel = std::mem::replace(&mut self.cancel, Box::new(|| ()));
        cancel();
    }
}
