use std::collections::HashMap;

use bytes::Bytes;

/// A chunk in a streaming response from a PHP worker.
///
/// The worker emits a sequence ending with [`End`]. Consumers read until `End`
/// (or the channel closes) to obtain the complete response.
///
/// A worker finalises a request in one of three shapes:
///
/// * **streaming** — one [`Headers`] chunk followed by zero or more [`Body`]
///   chunks (the PHP `folk_write_head` / `folk_write` API);
/// * **return** — a single [`Return`] chunk carrying the handler's return value
///   verbatim (non-streaming handlers: gRPC, jobs, and HTTP that `return`s an
///   array);
/// * **error** — a single [`Error`] chunk carrying a fatal [`WorkerError`]
///   (uncaught exception, dispatch failure, malformed response).
///
/// Each shape is terminated by [`End`]. Consumers interpret `Return` themselves:
/// the HTTP plugin decodes the `{status, headers, body}` convention, while
/// gRPC/jobs use the value as-is.
///
/// [`Headers`]: ResponseChunk::Headers
/// [`Body`]: ResponseChunk::Body
/// [`Return`]: ResponseChunk::Return
/// [`Error`]: ResponseChunk::Error
/// [`End`]: ResponseChunk::End
#[derive(Debug, Clone)]
pub enum ResponseChunk {
    /// Response status and headers. Must be the first chunk (after optional EarlyHints).
    Headers {
        status: u16,
        headers: HashMap<String, String>,
    },
    /// A piece of the response body.
    Body(Bytes),
    /// The worker's return value, passed through verbatim.
    ///
    /// Used by non-streaming handlers. The consumer decides how to interpret it
    /// (the HTTP plugin decodes `{status, headers, body}`; gRPC/jobs use it
    /// directly). Followed by [`End`](ResponseChunk::End).
    Return(serde_json::Value),
    /// A fatal worker error (uncaught exception / dispatch failure).
    ///
    /// Distinct from a business outcome, which travels as a normal
    /// [`Return`](ResponseChunk::Return). Consumers translate this into a
    /// transport-level failure (HTTP 500, gRPC INTERNAL). Followed by
    /// [`End`](ResponseChunk::End).
    Error(WorkerError),
    /// Signals that the response is complete.
    End,
}

/// A fatal error raised while a PHP worker handled a request.
///
/// Carries the human-readable message and, **in dev mode only**, the exception
/// class and stack trace. In production `exception_class` / `stacktrace` are
/// `None` to avoid leaking internals to clients; the full detail is still
/// logged server-side.
#[derive(Debug, Clone)]
pub struct WorkerError {
    /// Human-readable error message (the exception message, or a dispatch
    /// failure reason).
    pub message: String,
    /// Fully-qualified PHP exception class (dev mode only).
    pub exception_class: Option<String>,
    /// Stack trace as a string (dev mode only — info leak in production).
    pub stacktrace: Option<String>,
}

impl WorkerError {
    /// Construct an error with just a message (no class/trace).
    pub fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
            exception_class: None,
            stacktrace: None,
        }
    }
}

impl std::fmt::Display for WorkerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.exception_class {
            Some(class) => write!(f, "{class}: {}", self.message),
            None => f.write_str(&self.message),
        }
    }
}

impl std::error::Error for WorkerError {}
