//! Channel adapters: downstream consumers of Escalation tickets.
//!
//! `JobQueueEscalation` writes tickets to a `JobQueue` subject; a
//! `ChannelAdapter` (one of `LogChannel`, `WebhookChannel`,
//! `JobQueueChannel`) listens on that subject and delivers tickets
//! to the operator-facing destination (stdout, HTTP webhook,
//! downstream JobQueue subject). Multiple adapters can subscribe to
//! the same subject — a deployment typically wires one HTTP webhook
//! for human pages plus one JobQueue passthrough into an internal
//! ticketing service.

mod jobqueue;
mod log;
mod webhook;

pub use jobqueue::JobQueueChannel;
pub use log::LogChannel;
pub use webhook::WebhookChannel;

use crate::escalation::trait_::EscalationTicket;
use async_trait::async_trait;
use thiserror::Error;

/// Errors raised by channel adapters.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ChannelError {
    /// Downstream destination unreachable (HTTP failure, queue write
    /// rejection, log sink broken).
    #[error("channel unavailable: {0}")]
    Unavailable(String),
    /// Other internal.
    #[error("internal: {0}")]
    Internal(String),
    /// Supplied URL is not a valid absolute HTTP/HTTPS URL.
    #[error("invalid webhook URL: {0}")]
    InvalidUrl(String),
}

/// Channel adapter trait. Implementations consume tickets and deliver
/// them. The `subscribe` method is intentionally `async fn` and not
/// stream-typed; channel adapters often run as long-lived tasks that
/// loop over `JobQueue::claim` + dispatch.
#[async_trait]
pub trait ChannelAdapter: Send + Sync {
    /// Deliver a single ticket. Idempotent in spirit — if delivery fails,
    /// the caller may retry; downstream destinations should tolerate
    /// at-least-once.
    async fn deliver(&self, ticket: &EscalationTicket) -> Result<(), ChannelError>;

    /// Stable name for the adapter (used in audit + metric labels).
    fn name(&self) -> &'static str;
}
