//! [`EmitPublisher`] — delivers a saga's external (Regime-B) emits to the
//! outside world (P3.2 slice 2).
//!
//! When a step emits a request to an external worker and suspends, the request
//! is durably written to `es_saga_outbox` in the same transaction as the
//! envelope event (slice 1). A [`SagaWorker`](crate::SagaWorker) configured
//! with an `EmitPublisher` drains the unpublished rows in id order, calls
//! [`publish`](EmitPublisher::publish) for each, then stamps `published_at`.
//! **Publish-then-mark ⇒ at-least-once:** a crash between publish and mark
//! re-publishes on the next drain, so `publish` (or the downstream consumer)
//! must be idempotent — dedupe on `(instance_id, step)` or the
//! [`IdempotencyKey`](crate::IdempotencyKey) the consumer echoes back.

use std::future::Future;
use std::pin::Pin;

/// One pending external request to deliver — a view over an `es_saga_outbox`
/// row, handed to [`EmitPublisher::publish`]. Borrowed: the worker owns the
/// row data for the duration of the call.
pub struct OutboxMessage<'a> {
    pub saga_name: &'a str,
    pub instance_id: &'a str,
    /// The step that emitted the request.
    pub step: &'a str,
    /// The request's event type (the `EventData::event_type`).
    pub event_type: &'a str,
    /// The request payload as recorded on the envelope.
    pub payload: &'a serde_json::Value,
}

/// Delivers external saga emits to a transport (a queue, an HTTP endpoint, …).
///
/// Implement `publish` to send `msg` onward; return `Ok(())` once it is safely
/// handed off (then the worker marks the row published) or `Err` to retry on a
/// later drain. Delivery is **at-least-once** — make `publish` idempotent.
pub trait EmitPublisher: Send + Sync + 'static {
    fn publish(&self, msg: OutboxMessage<'_>) -> impl Future<Output = anyhow::Result<()>> + Send;
}

/// Object-safe shim so a publisher of any concrete type can be stored as
/// `Arc<dyn EmitPublisherDyn>` on the runner (same native-trait + boxed-future
/// pattern as `mire`'s `EventHandler`/`EventHandlerDyn`).
pub(crate) trait EmitPublisherDyn: Send + Sync {
    fn publish_dyn<'a>(
        &'a self,
        msg: OutboxMessage<'a>,
    ) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>>;
}

pub(crate) struct PublisherAdapter<P: EmitPublisher>(pub P);

impl<P: EmitPublisher> EmitPublisherDyn for PublisherAdapter<P> {
    fn publish_dyn<'a>(
        &'a self,
        msg: OutboxMessage<'a>,
    ) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>> {
        Box::pin(self.0.publish(msg))
    }
}
