//! Typed error enum for the WebSocket service layer.
//!
//! Every distinct failure mode the WS layer can produce is represented here.
//! The HTTP/WS adapter maps these to close codes or error frames.

/// Errors produced by the WebSocket service layer.
///
/// Each variant maps to a distinct failure mode; the transport adapter
/// translates these into WS close codes or error-frame messages.
#[derive(Debug)]
#[allow(dead_code)]
pub enum Error {
    /// Client sent a message that could not be parsed as a valid `WsRequest`.
    /// Maps to an `error` frame with the parse failure detail.
    InvalidMessage(String),
    /// The chat subsystem returned an error (e.g. LLM provider failure).
    /// Maps to an `error` frame with the provider message.
    Chat(String),
    /// A permission response referenced an unknown `request_id`.
    /// Silently ignored in practice (no frame sent), but tracked for observability.
    UnknownPermissionRequest(String),
    /// Failed to load initial state (pipeline, onboarding, wizard).
    /// Maps to an `error` frame.
    Init(String),
    /// The send channel to the client is closed (client disconnected).
    /// Triggers connection teardown — no frame is sent.
    ClientGone,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidMessage(msg) => write!(f, "Invalid request: {msg}"),
            Self::Chat(msg) => write!(f, "Chat error: {msg}"),
            Self::UnknownPermissionRequest(id) => {
                write!(f, "Unknown permission request: {id}")
            }
            Self::Init(msg) => write!(f, "Initialisation error: {msg}"),
            Self::ClientGone => write!(f, "Client disconnected"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn display_invalid_message() {
        let err = Error::InvalidMessage("bad json".to_string());
        assert_eq!(err.to_string(), "Invalid request: bad json");
    }

    #[test]
    fn display_chat_error() {
        let err = Error::Chat("timeout".to_string());
        assert_eq!(err.to_string(), "Chat error: timeout");
    }

    #[test]
    fn display_unknown_permission_request() {
        let err = Error::UnknownPermissionRequest("req-99".to_string());
        assert_eq!(err.to_string(), "Unknown permission request: req-99");
    }

    #[test]
    fn display_init_error() {
        let err = Error::Init("CRDT not ready".to_string());
        assert_eq!(err.to_string(), "Initialisation error: CRDT not ready");
    }

    #[test]
    fn display_client_gone() {
        let err = Error::ClientGone;
        assert_eq!(err.to_string(), "Client disconnected");
    }

    #[test]
    fn error_is_debug() {
        let err = Error::InvalidMessage("test".to_string());
        let debug = format!("{err:?}");
        assert!(debug.contains("InvalidMessage"));
    }

    #[test]
    fn all_variants_display_without_panic() {
        let variants: Vec<Error> = vec![
            Error::InvalidMessage("a".to_string()),
            Error::Chat("b".to_string()),
            Error::UnknownPermissionRequest("c".to_string()),
            Error::Init("d".to_string()),
            Error::ClientGone,
        ];
        for v in &variants {
            let _ = v.to_string();
        }
    }
}
