///|
/// OpenList 的统一响应信封 `{code, message, data}`。
///
/// 只在 core 内部使用:域子模块拿到的是解码后的业务类型或 `OpenListError`。
priv struct Envelope {
  /// 业务结果码;`200` 表示成功。
  code : Int
  /// 业务消息;成功时服务端固定给 `"success"`。
  message : String
  /// 业务载荷;字段缺失或为 `null` 时是 `None`。
  data : Json?
}

///|
/// 截断过长的响应文本,避免把整个 HTML 错误页塞进异常消息。
fn preview(text : String) -> String {
  let chars = text.to_array()
  if chars.length() <= 200 {
    text
  } else {
    "\{String::from_array(chars[:200])}…"
  }
}

///|
/// 把响应文本解析成信封。
///
/// 不是 JSON 对象、或缺少数值型 `code` 都算协议错误(`Decode`):前者通常
/// 意味着请求打到了反代 / 路由不存在的 404 页面(OpenList 对未知路径会返回
/// 前端 HTML),后者说明服务端不是 OpenList。消息里带上 HTTP 状态,方便区分
/// 「服务端版本没有这个路由」和「真的解析失败」。
fn Envelope::parse(status : Int, text : String) -> Envelope raise OpenListError {
  let json = @json.parse(text) catch {
    _ =>
      raise OpenListError::Decode(
        "HTTP \{status}:响应不是合法 JSON:\{preview(text)}",
      )
  }
  guard json is Object(fields) else {
    raise OpenListError::Decode(
      "HTTP \{status}:响应不是 JSON 对象:\{preview(text)}",
    )
  }
  let code : Int = match fields.get("code") {
    Some(value) =>
      @json.from_json(value) catch {
        _ =>
          raise OpenListError::Decode(
            "HTTP \{status}:响应缺少整数 code:\{preview(text)}",
          )
      }
    None =>
      raise OpenListError::Decode(
        "HTTP \{status}:响应缺少 code 字段:\{preview(text)}",
      )
  }
  let message : String = match fields.get("message") {
    Some(String(value)) => value
    _ => ""
  }
  let data : Json? = match fields.get("data") {
    Some(Json::Null) => None
    Some(value) => Some(value)
    None => None
  }
  { code, message, data, }
}

///|
/// 信封里的 `data`;缺失或 `null` 时退化成 `Json::Null`。
fn Envelope::data_or_null(self : Envelope) -> Json {
  match self.data {
    Some(value) => value
    None => Json::null()
  }
}

///|
/// `code` 不是 200 就抛业务错误。
fn Envelope::check(self : Envelope, status : Int) -> Unit raise OpenListError {
  if self.code != 200 {
    raise OpenListError::Api(ApiError::{
      code: self.code,
      message: self.message,
      status,
      data: self.data,
    })
  }
}