//! Server-Sent Events (SSE) support for the `ordinary-sse` feature.
//!
//! SSE is an HTTP-based server-push protocol where the server sends a stream
//! of text events to the client over a long-lived HTTP connection.
//!
//! This module provides:
//! - `SseSender` — push events to the client.
//! - `SseEvent` — a single SSE event with optional event name, id, and retry.
//!
//! # Example
//!
//! ```ignore
//! #[sse(desc("Live notifications"))]
//! async fn notifications(sender: SseSender) -> afast::Result<()> {
//!     for i in 0..10 {
//!         sender.send(&format!("event #{}", i)).await?;
//!         tokio::time::sleep(std::time::Duration::from_secs(1)).await;
//!     }
//!     Ok(())
//! }
//! ```

use crate::error::Error;
use std::fmt::Write;

// ─── SseEvent ─────────────────────────────────────────────────────

/// A single Server-Sent Event.
///
/// Fields map directly to the SSE wire format:
/// - `event:` — event type (optional)
/// - `data:` — payload (required)
/// - `id:` — last-event-id for reconnection (optional)
/// - `retry:` — reconnection time in ms (optional)
pub struct SseEvent {
    /// The event type name (maps to `event:` field).
    pub event: Option<String>,
    /// The event payload (maps to `data:` field).
    pub data: String,
    /// The event ID (maps to `id:` field).
    pub id: Option<String>,
    /// Reconnection time in milliseconds (maps to `retry:` field).
    pub retry: Option<u64>,
}

impl SseEvent {
    /// Formats this event into the SSE wire format.
    ///
    /// ```text
    /// event: update\n
    /// id: 123\n
    /// retry: 5000\n
    /// data: {"key":"value"}\n
    /// \n
    /// ```
    pub fn to_wire(&self) -> String {
        let mut out = String::with_capacity(128);
        if let Some(ref event) = self.event {
            out.push_str("event: ");
            out.push_str(event);
            out.push('\n');
        }
        if let Some(ref id) = self.id {
            out.push_str("id: ");
            out.push_str(id);
            out.push('\n');
        }
        if let Some(retry) = self.retry {
            out.push_str("retry: ");
            write!(out, "{}", retry).unwrap();
            out.push('\n');
        }
        // Multi-line data: split by newlines, each line gets a `data:` prefix.
        for line in self.data.split('\n') {
            out.push_str("data: ");
            out.push_str(line);
            out.push('\n');
        }
        out.push('\n');
        out
    }
}

// ─── SseSender ────────────────────────────────────────────────────

/// Sender for pushing Server-Sent Events to the client.
///
/// Obtained as a handler parameter in `#[sse]` handlers. The sender
/// writes formatted SSE text frames into the HTTP response body stream.
#[derive(Clone)]
pub struct SseSender {
    tx: tokio::sync::mpsc::Sender<String>,
}

impl SseSender {
    /// Creates a new `SseSender` wrapping the given channel.
    #[cfg(all(feature = "http", feature = "binary"))]
    pub(crate) fn new(tx: tokio::sync::mpsc::Sender<String>) -> Self {
        Self { tx }
    }

    /// Sends a `data:` event with the given value serialized as JSON.
    ///
    /// If serialization fails, the raw debug string is sent instead.
    pub async fn send<T: serde::Serialize>(&self, data: &T) -> Result<(), Error> {
        let payload =
            serde_json::to_string(data).unwrap_or_else(|_| "<unserializable>".to_string());
        let wire = SseEvent {
            event: None,
            data: payload,
            id: None,
            retry: None,
        }
        .to_wire();
        self.tx.send(wire).await.map_err(|_| Error::Ws {
            message: "sse channel closed".into(),
        })
    }

    /// Sends an event with a custom event type name.
    pub async fn send_event<T: serde::Serialize>(
        &self,
        event: &str,
        data: &T,
    ) -> Result<(), Error> {
        let payload =
            serde_json::to_string(data).unwrap_or_else(|_| "<unserializable>".to_string());
        let wire = SseEvent {
            event: Some(event.to_string()),
            data: payload,
            id: None,
            retry: None,
        }
        .to_wire();
        self.tx.send(wire).await.map_err(|_| Error::Ws {
            message: "sse channel closed".into(),
        })
    }

    /// Sends a raw `SseEvent` with full control over all fields.
    pub async fn send_raw(&self, event: SseEvent) -> Result<(), Error> {
        let wire = event.to_wire();
        self.tx.send(wire).await.map_err(|_| Error::Ws {
            message: "sse channel closed".into(),
        })
    }

    /// Closes the SSE stream. The client will see the connection close.
    pub async fn close(&self) {
        let _ = self.tx.send(String::new()).await;
    }
}

// ─── SseHandlerInvoker trait ─────────────────────────────────────

/// Trait for invoking SSE handler functions.
///
/// Generated by the `#[sse(...)]` macro. The invoker extracts HTTP
/// parameters (Query, Param, Header) from the upgrade request,
/// creates an `SseSender`, and calls the user's handler function.
pub trait SseHandlerInvoker: Send + Sync {
    /// Invokes the SSE handler.
    ///
    /// `query` — raw query string from the request URL.
    /// `path_params` — captured path parameters from route matching.
    /// `headers` — request headers as a JSON object.
    /// `sender` — the SSE sender to push events.
    /// `state` — shared application state.
    fn call_sse(
        &'static self,
        query: &str,
        path_params: &std::collections::HashMap<String, String>,
        headers: serde_json::Value,
        sender: SseSender,
        state: std::sync::Arc<crate::state::StateMap>,
        ctx: crate::ctx::RequestCtx,
    ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'static>>;
}

use std::future::Future;

// ─── SseRouteInfo ────────────────────────────────────────────────

/// Stores information about a registered SSE route.
#[derive(Clone)]
pub struct SseRouteInfo {
    /// The path pattern (e.g. `"/notifications/:user_id"`).
    pub path: &'static str,
    /// The handler name for logging/diagnostics.
    pub handler_name: &'static str,
    /// The compiled route pattern.
    pub(crate) pattern: crate::app::ordinary::RoutePattern,
    /// The SSE handler invoker.
    pub invoker: &'static dyn SseHandlerInvoker,
    /// The service this route belongs to.
    pub service_name: String,
    /// Custom attributes from handler macros.
    pub attrs: &'static [crate::handler::Attr],
}
