//! Response types for AT-Jet
//!
//! Provides Protobuf-aware response types for axum handlers.

use {crate::content_types::APPLICATION_PROTOBUF,
     axum::{http::{StatusCode,
                   header::CONTENT_TYPE},
            response::{IntoResponse,
                       Response}},
     prost::Message};

/// Protobuf response wrapper
///
/// Encodes a Protobuf message and returns it with the correct content type.
///
/// # Example
///
/// ```rust,ignore
/// use at_jet::prelude::*;
///
/// async fn get_user(Path(id): Path<i32>) -> ProtobufResponse<User> {
///     let user = User { id, name: "John".to_string() };
///     ProtobufResponse::ok(user)
/// }
///
/// async fn create_user(
///     ProtobufRequest(req): ProtobufRequest<CreateUserRequest>
/// ) -> ProtobufResponse<User> {
///     let user = User { id: 1, name: req.name };
///     ProtobufResponse::created(user)
/// }
/// ```
pub struct ProtobufResponse<T>
where
  T: Message, {
  status:  StatusCode,
  message: T,
}

impl<T> ProtobufResponse<T>
where
  T: Message,
{
  /// Create a new response with custom status code
  pub fn new(status: StatusCode, message: T) -> Self {
    Self { status, message }
  }

  /// Create a 200 OK response
  pub fn ok(message: T) -> Self {
    Self::new(StatusCode::OK, message)
  }

  /// Create a 201 Created response
  pub fn created(message: T) -> Self {
    Self::new(StatusCode::CREATED, message)
  }

  /// Create a 202 Accepted response
  pub fn accepted(message: T) -> Self {
    Self::new(StatusCode::ACCEPTED, message)
  }
}

impl<T> IntoResponse for ProtobufResponse<T>
where
  T: Message,
{
  fn into_response(self) -> Response {
    let bytes = self.message.encode_to_vec();

    (self.status, [(CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes).into_response()
  }
}

/// Result type that can return either a Protobuf response or an error
pub type ProtobufResult<T> = Result<ProtobufResponse<T>, crate::error::JetError>;

/// Empty response (204 No Content)
pub struct NoContent;

impl IntoResponse for NoContent {
  fn into_response(self) -> Response {
    StatusCode::NO_CONTENT.into_response()
  }
}

/// Protobuf response with optional body
pub enum MaybeProtobuf<T>
where
  T: Message, {
  /// Response with body
  Some(ProtobufResponse<T>),
  /// No content response
  None,
}

impl<T> IntoResponse for MaybeProtobuf<T>
where
  T: Message,
{
  fn into_response(self) -> Response {
    match self {
      | MaybeProtobuf::Some(response) => response.into_response(),
      | MaybeProtobuf::None => StatusCode::NO_CONTENT.into_response(),
    }
  }
}

/// Helper trait for creating Protobuf responses from messages
pub trait IntoProtobufResponse: Message + Sized {
  /// Convert to a 200 OK Protobuf response
  fn into_pb_response(self) -> ProtobufResponse<Self> {
    ProtobufResponse::ok(self)
  }

  /// Convert to a Protobuf response with custom status
  fn into_pb_response_with_status(self, status: StatusCode) -> ProtobufResponse<Self> {
    ProtobufResponse::new(status, self)
  }
}

// Implement for all Message types
impl<T: Message> IntoProtobufResponse for T {}
