///|
/// 这两个类型出现在 `PreparedRequest` / `RawResponse` 的公开签名里,
/// 因此随本包一起再导出,使用者不必为了构造一份请求再 import 两个包。
/// `CancelToken` 同理:它由 `PreparedRequest` 携带到这一层。
pub using @config {type Method, type ProgressCallback, type CancelToken}

///|
pub using @headers {type Headers}

///|
/// 走代理需要的两样东西:连到哪儿、以及 CONNECT 请求带什么凭据。
///
/// 已经是**解析完的最小形态**:协议名与端口默认值在拼请求时就写进了 `url`,
/// 凭据也已经编码成可直接落头的字符串。这样传输层不必认识 `ProxyProtocol`
/// 这类配置枚举,也不必知道 Basic 认证怎么编码——与 `PreparedRequest`
/// 「不含任何配置语义」的口径一致。
pub(all) struct ProxyEndpoint {
  /// 代理服务器地址,形如 `http://127.0.0.1:9000`;
  /// 不带端口时由底层按协议补默认端口(http 80 / https 443)
  url : String
  /// 建立隧道用的 `Proxy-Authorization` 头值(如 `Basic ...`);
  /// `None` 表示匿名代理
  authorization : String?
} derive(Debug)

///|
pub extend ProxyEndpoint with @debug.Debug::{to_repr}

///|
/// 交给传输层去发送的一份请求。
///
/// 到达这一步时,配置合并、`base_url` 拼接、query 序列化、头拍平都已经完成,
/// 所以它只包含「把字节发出去」真正需要的信息,不含任何配置语义。
/// 这样任何实现——真实的 HTTP、测试用的 Mock、将来可能的连接池——
/// 都只需要关心这一层,替换传输实现不会影响上层语义。
pub(all) struct PreparedRequest {
  /// 请求方法(与 Config 一样用 http_method,避开 MoonBit 的保留字 method)
  http_method : @config.Method
  /// 完整地址,已包含 `base_url` 拼接结果与序列化后的 query
  url : String
  /// 已按 common < 按方法 < 请求级 拍平的最终头集合
  headers : @headers.Headers
  /// 请求体字节;`None` 表示不带 body
  body : Bytes?
  /// 超时毫秒数;`None` 或 `<= 0` 表示不限时。
  ///
  /// 分两段生效:建连到拿到响应头这一段整体受它约束,响应体每次读取也受
  /// 它约束(见 `ResponseBody`)。所以长连的 SSE 请求应当传 `None`。
  /// 走代理时**建连包含与代理的 CONNECT 握手**,它同样落在这段时限里。
  timeout : Int?
  /// 代理服务器;`None` 表示直连。
  ///
  /// 有代理时流量全部经代理转发,http 目标也走 CONNECT 隧道(见 `docs/09-proxy.md`)。
  proxy : ProxyEndpoint?
  /// 上传进度回调;`None` 表示这次请求不报告进度。
  ///
  /// **由传输实现负责调用**:在把 `body` 写出去的过程中逐块报告。
  /// 内置的 `AsyncHttpTransport` 会按 64 KiB 分块写入并逐块回调;
  /// 自定义实现可以不调用它(测试用的 `MockTransport` 就不调用——
  /// 它不真的发送数据,所以上传进度无从报告)。
  /// 语义与粒度见 `docs/10-progress.md`。
  on_upload_progress : ProgressCallback?
  /// 这次请求的取消句柄;`None` 表示不可取消。
  ///
  /// **由传输实现负责尊重它**:内置的 `AsyncHttpTransport` 在发送每一跳、
  /// 以及响应体每次读取时把中断手段登记到它上面,取消一旦发生,挂起中的
  /// 连接动作会立刻被中断,对外表现为 `TransportError::Cancelled`。
  /// 自定义实现可以不理会它(测试用的 `MockTransport` 就不理会——它不真的
  /// 做 I/O,没有「挂起」可中断;这种情况下取消仍然由上层在请求进入管线时
  /// 的预检查兜住「已取消的 token」这一种情况)。
  /// 语义与覆盖范围见 `docs/12-cancellation.md`。
  cancel_token : CancelToken?
}

///|
/// 手写 Debug 而不是 `derive(Debug)`:`on_upload_progress` 是函数类型,
/// 函数没有可打印的内容。两处刻意与默认渲染不同:
/// - 回调渲染成 `` / ``(与 `Config` 对函数字段的处理一致);
/// - `body` 只给字节数,`proxy` 只给地址:前者可能是整个上传文件的内容,
///   后者的 `authorization` 是一份凭据,都不该整段进日志。
pub impl @debug.Debug for PreparedRequest with fn to_repr(self) {
  let body = match self.body {
    Some(bytes) =>
      @debug.Repr::opaque_(
        "Bytes",
        @debug.Repr::integer(bytes.length().to_string()),
      )
    None => @debug.Repr::literal("None")
  }
  let fields : Map[String, @debug.Repr] = Map([])
  fields["http_method"] = @debug.Repr::string(self.http_method.to_string())
  fields["url"] = @debug.Repr::string(self.url)
  fields["headers"] = @debug.Repr::string(self.headers.to_string())
  fields["body"] = body
  fields["timeout"] = match self.timeout {
    Some(milliseconds) => @debug.Repr::integer(milliseconds.to_string())
    None => @debug.Repr::literal("None")
  }
  fields["proxy"] = match self.proxy {
    Some(endpoint) =>
      @debug.Repr::opaque_("ProxyEndpoint", @debug.Repr::string(endpoint.url))
    None => @debug.Repr::literal("None")
  }
  fields["on_upload_progress"] = match self.on_upload_progress {
    Some(_) => @debug.Repr::literal("")
    None => @debug.Repr::literal("")
  }
  // token 与回调一样只区分「有没有」:它的取消状态会随外部调用变化,
  // 渲染进去会让同一份请求在不同时刻给出不同字符串。
  fields["cancel_token"] = match self.cancel_token {
    Some(_) => @debug.Repr::literal("")
    None => @debug.Repr::literal("")
  }
  @debug.Repr::opaque_("PreparedRequest", @debug.Repr::record(fields))
}

///|
pub extend PreparedRequest with @debug.Debug::{to_repr}

///|
/// 传输层拿到的原始响应。
///
/// 刻意不叫 `Response`:上层对外的 `Response` 还要承担 JSON 解析、
/// 状态码校验等语义,那些不属于传输层的职责。
///
/// `body` 是**流**而不是字节:`send` 返回时只保证状态行与响应头已到手,
/// 响应体按需读取(`ResponseBody`)。底层原语只保留最弱的能力,
/// 一次性读全是上层的组合结果——这样 chunked / SSE 这类「边到边读」
/// 的协议才能表达出来。
pub(all) struct RawResponse {
  status : Int
  status_text : String
  headers : @headers.Headers
  body : ResponseBody
} derive(Debug)

///|
pub extend RawResponse with @debug.Debug::{to_repr}

///|
/// 传输层错误:把「网络世界里可能出什么事」收敛成四种情况,
/// 上层再映射成对外的 `ErrorCode`。
///
/// 有了这层收敛,上层不需要 import 任何 async 相关的包,
/// 自定义传输实现也不需要知道底层用的是哪套 HTTP 库。
pub(all) suberror TransportError {
  /// 超时(底层抛出的超时会被映射到这里)
  Timeout
  /// 网络层失败:连接失败、DNS 解析失败、TLS 握手失败等
  Network(String)
  /// 请求还没发出去就失败了:协议不支持、地址非法等
  Unsupported(String)
  /// 被 `cancel_token` 取消了。
  ///
  /// 不带文案:取消理由(`CancelToken::reason`)由上层连同**合并后的配置**
  /// 一起翻译,默认文案与「HTTP 状态码类错误」的文案保持在同一处
  /// (`src/http_error.mbt`)。这里只表达「这次失败是取消,不是网络故障」,
  /// 上层据此报 `ERR_CANCELED` 而不是 `ERR_NETWORK`。
  Cancelled
}

///|
/// 传输层抽象:把一份准备好的请求发出去,拿回原始响应。
///
/// 用 trait 而不是直接调用异步 HTTP 库有两个目的:
/// 1. 把整个 async 运行时依赖关在实现里,上层(配置合并、URL 拼接、
///    错误映射)都能用普通同步测试覆盖;
/// 2. 使用方和测试可以注入自己的实现,不必真的发网络请求。
pub(open) trait Transport {
  async fn send(Self, PreparedRequest) -> RawResponse raise TransportError
}