/// All errors produced by the Monk SDK.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MonkError {
    /// A client-side validation error (bad input before any network call).
    #[error("validation error: {0}")]
    Validation(String),

    /// The API returned a structured error response.
    #[error("API error {status} [{code}]: {message} (request_id={request_id})")]
    Api {
        status: u16,
        code: String,
        message: String,
        request_id: String,
        retryable: bool,
    },

    /// A network-level error (DNS, connection refused, TLS, etc.).
    #[error("network error: {0}")]
    Network(#[from] reqwest::Error),

    /// The request timed out (distinct from a network error for retry classification).
    #[error("request timed out")]
    Timeout,

    /// The circuit breaker is open — the server is assumed to be down.
    #[error("circuit breaker open: server assumed unavailable")]
    CircuitOpen,

    /// The in-memory event buffer is full.
    #[error("event buffer full ({capacity} events)")]
    BufferFull { capacity: usize },

    /// Failed to serialize the request body.
    #[error("serialization error: {0}")]
    Serialization(#[from] serde_json::Error),
}

impl MonkError {
    /// Whether this error is safe and worthwhile to retry.
    pub fn is_retryable(&self) -> bool {
        match self {
            MonkError::Api { retryable, .. } => *retryable,
            MonkError::Network(e) => e.is_timeout() || e.is_connect() || e.is_request(),
            MonkError::Timeout => true,
            // Non-retryable: validation, serialization, buffer full, circuit open
            MonkError::Validation(_)
            | MonkError::Serialization(_)
            | MonkError::BufferFull { .. }
            | MonkError::CircuitOpen => false,
        }
    }

    /// Build an `Api` variant from status code and parsed error body.
    pub(crate) fn from_api(status: u16, body: crate::models::ApiErrorBody) -> Self {
        let retryable = classify_retryable(status, body.retry);
        MonkError::Api {
            status,
            code: body.error.code,
            message: body.error.message,
            request_id: body.request_id,
            retryable,
        }
    }

    /// Build an `Api` variant when the response body couldn't be parsed.
    pub(crate) fn from_status(status: u16, raw_body: String) -> Self {
        MonkError::Api {
            status,
            code: "unknown".into(),
            message: raw_body,
            request_id: String::new(),
            retryable: is_server_error(status),
        }
    }
}

fn classify_retryable(status: u16, server_hint: bool) -> bool {
    if server_hint {
        return true;
    }
    is_server_error(status) || status == 429
}

fn is_server_error(status: u16) -> bool {
    matches!(status, 500 | 502 | 503 | 504)
}

/// Convenience alias used throughout the SDK.
pub type Result<T> = std::result::Result<T, MonkError>;
