///|
pub(all) struct ApiError {
  code : Int
  message : String
  details : Array[Map[String, String]]?
} derive(Debug, ToJson)

///|
pub let ok : Int = 0

///|
pub let cancelled : Int = 1

///|
pub let unknown : Int = 2

///|
pub let invalid_argument : Int = 3

///|
pub let deadline_exceeded : Int = 4

///|
pub let not_found : Int = 5

///|
pub let already_exists : Int = 6

///|
pub let permission_denied : Int = 7

///|
pub let resource_exhausted : Int = 8

///|
pub let failed_precondition : Int = 9

///|
pub let aborted : Int = 10

///|
pub let out_of_range : Int = 11

///|
pub let unimplemented : Int = 12

///|
pub let internal : Int = 13

///|
pub let unavailable : Int = 14

///|
pub let data_loss : Int = 15

///|
pub let unauthenticated : Int = 16

///|
pub fn ApiError::new(code : Int, message : String) -> ApiError {
  { code, message, details: None }
}

///|
/// Convenience constructors for the 16 canonical `ApiError` codes.
/// Each one is equivalent to `ApiError::new(, msg)` with the code
/// baked in, so the code cannot be mistyped. For custom codes, keep using
/// `ApiError::new` together with `register`.
pub fn ApiError::cancelled(msg : String) -> ApiError {
  ApiError::new(cancelled, msg)
}

///|
pub fn ApiError::unknown(msg : String) -> ApiError {
  ApiError::new(unknown, msg)
}

///|
pub fn ApiError::invalid_argument(msg : String) -> ApiError {
  ApiError::new(invalid_argument, msg)
}

///|
pub fn ApiError::deadline_exceeded(msg : String) -> ApiError {
  ApiError::new(deadline_exceeded, msg)
}

///|
pub fn ApiError::not_found(msg : String) -> ApiError {
  ApiError::new(not_found, msg)
}

///|
pub fn ApiError::already_exists(msg : String) -> ApiError {
  ApiError::new(already_exists, msg)
}

///|
pub fn ApiError::permission_denied(msg : String) -> ApiError {
  ApiError::new(permission_denied, msg)
}

///|
pub fn ApiError::resource_exhausted(msg : String) -> ApiError {
  ApiError::new(resource_exhausted, msg)
}

///|
pub fn ApiError::failed_precondition(msg : String) -> ApiError {
  ApiError::new(failed_precondition, msg)
}

///|
pub fn ApiError::aborted(msg : String) -> ApiError {
  ApiError::new(aborted, msg)
}

///|
pub fn ApiError::out_of_range(msg : String) -> ApiError {
  ApiError::new(out_of_range, msg)
}

///|
pub fn ApiError::unimplemented(msg : String) -> ApiError {
  ApiError::new(unimplemented, msg)
}

///|
pub fn ApiError::internal(msg : String) -> ApiError {
  ApiError::new(internal, msg)
}

///|
pub fn ApiError::unavailable(msg : String) -> ApiError {
  ApiError::new(unavailable, msg)
}

///|
pub fn ApiError::data_loss(msg : String) -> ApiError {
  ApiError::new(data_loss, msg)
}

///|
pub fn ApiError::unauthenticated(msg : String) -> ApiError {
  ApiError::new(unauthenticated, msg)
}

///|
let codes : Map[Int, Int] = Map([])

///|
fn init {
  codes[ok] = 200
  codes[cancelled] = 499
  codes[unknown] = 500
  codes[invalid_argument] = 400
  codes[failed_precondition] = 400
  codes[out_of_range] = 400
  codes[deadline_exceeded] = 504
  codes[not_found] = 404
  codes[already_exists] = 409
  codes[permission_denied] = 403
  codes[unauthenticated] = 401
  codes[resource_exhausted] = 429
  codes[aborted] = 409
  codes[unimplemented] = 501
  codes[internal] = 500
  codes[unavailable] = 503
  codes[data_loss] = 500
}

///|
pub fn ApiError::to_http_status(code : Int) -> Int {
  match codes.get(code) {
    Some(status) => status
    None => 500
  }
}

///|
pub fn register(code : Int, http_status : Int) -> Unit {
  codes[code] = http_status
}

///|
/// Types that can be converted to an `ApiError` for use with `reply_error`.
/// `reply_error` accepts any `T : ToApiError`, so you can pass `PonyError`,
/// `ApiError`, or your own error types directly.
pub(open) trait ToApiError {
  fn to_api_error(Self) -> ApiError
}

///|
pub impl ToApiError for ApiError with fn to_api_error(self : ApiError) -> ApiError {
  self
}

///|
/// Convert a `PonyError` to an `ApiError`.
/// Kept for backward compatibility — delegates to the `ToApiError` trait.
pub fn to_api_error(e : PonyError) -> ApiError {
  ToApiError::to_api_error(e)
}