// ============================================================================
// 两个流式入口:`Client::stream`(原始字节流)与 `Client::sse`(事件流)。
//
// 它们与 `request` 共用前半段(合并配置 → 取消预检查 → 请求拦截器 → 发送 →
// 重定向),差别是**不读响应体**,也不跑响应侧链:手里是还没读的字节流,
// 「救回一份 Response」没有落点(见 docs/11-interceptors.md)。
// ============================================================================

///|
/// 两个流式入口(`stream` / `sse`)共用的前半段:
/// 合并配置 → 定方法 → 请求拦截器 → 拼地址与头 → 发送(含自动跟随重定向)
/// → 校验状态码。
///
/// 请求拦截器的位置与 `request` 一致(**后注册先跑**);差别是被拦下时直接抛错,
/// 而不是交给响应侧错误处理器——两个流式入口没有响应侧链。
///
/// 重定向的处理与 `request` 完全一致(同一段循环,连上限与错误都一样):
/// 跟到 3xx 的最后一跳为止,交出去的 `config` 也是**最后一跳**的配置。
///
/// 状态码校验规则与 `request` 完全一致(默认只放行 2xx)。校验不通过时
/// 会把错误响应体读全(错误响应通常很短)再抛 `HttpError`——错误里仍然
/// 带着响应,状态码、响应头与错误原文都看得到。长连场景下这是唯一能
/// 立刻看到「为什么没连上」的地方,而不是一个裸状态码;连读错误体都失败
/// (超时、断连)时,错误里带着已经读到的部分,状态码与响应头照样在。
///
/// 与 `request` 的最后一步不同:这里**不读响应体**,把「还开着的连接」
/// 连同响应头一起返回,由调用方(两个流式入口)决定怎么读。
///
/// 压缩声明按流式路径的规则走(`buffered: false`):默认不声明,由底层实现补上
/// 并在响应头声明 gzip 时透明解压;关掉解压时声明 `identity` 挡住它。
async fn Client::open_stream(
  self : Client,
  config : Config,
  signal : AbortSignal?,
) -> (@transport.RawResponse, Config) raise HttpError {
  // 取消预检查的位置与 `request` 一致:合并配置之后、请求拦截器之前。
  // 两个流式入口共用这一条,所以 `stream` / `sse` 也被它覆盖。
  let merged = self.merged_config(config)
  check_cancelled(merged, signal)
  let merged = match self.interceptors.run_request(merged) {
    Ok(config) => config
    Err(error) => raise error
  }
  let (raw, merged) = self.send_following_redirects(merged, signal, false)
  if !@util.status_allowed(raw.status, merged) {
    // 错误体也要读全;读到一半失败(超时、断连)时把已读到的部分挂到
    // 错误上——那部分通常正是服务端写下的原因,比一个光秃秃的超时有用。
    let (body, failure) = raw.body.read_all_partial()
    let response = build_response(
      raw.status,
      raw.status_text,
      raw.headers,
      body,
      merged,
    )
    match failure {
      Some(error) => raise transport_error(error, merged, Some(response))
      None => ()
    }
    // 错误体不做额外处理:响应体以原始字节交出,`HttpError::response()` 上
    // 拿到的 `Response` 自己按配置的编码解(`text()`),不猜它「应该是什么格式」
    // ——错误体格式不可预期,强行解释只会把「状态码失败」这个更准确的原因盖掉。
    raise status_error(response.status, merged, response)
  }
  (raw, merged)
}

///|
/// 发起一次请求,但不读响应体,把**原始字节流**交给调用方。
///
/// 与 `request` 共用前半段(合并配置 → 定方法 → 拼地址/头/body → 发送 → 校验),
/// 区别是响应体不读:适合大文件下载、需要自己按块处理的场景。
/// 按 SSE 事件读请用 `Client::sse`——那是另一个协议,两个入口的类型也不同,
/// 免得把二进制数据喂进事件解析器(见 `sse_stream.mbt` 里 `SseStream` 的说明)。
///
/// 拿到 `StreamResponse` 之后要负责读到 EOF 或调用 `close()`,
/// 否则这条连接不会释放。
pub async fn Client::stream(
  self : Client,
  config : Config,
  signal? : AbortSignal,
) -> StreamResponse raise HttpError {
  let (raw, merged) = self.open_stream(config, signal)
  {
    status: raw.status,
    status_text: raw.status_text,
    headers: raw.headers,
    config: merged,
    body: raw.body,
  }
}

///|
/// 发起一次 SSE 请求,把解析好的**事件流**交给调用方。
///
/// 与 `stream` 共用前半段,多一道准入检查:响应头必须声明
/// `Content-Type: text/event-stream`。不是就立刻关掉连接并报
/// `ErrorCode::NotSupported`——「拿到的不是 SSE 却按 SSE 读」是使用错误,
/// 让它响亮失败,比把 JSON 或二进制解成一堆莫名其妙的事件好得多。
/// 确实要接一个不声明类型的服务端,可以用 `Client::stream` + 公开的
/// `@moonhttp.SseParser` 自己驱动(那正是解析器独立成包的好处)。
///
/// 这道检查失败时错误里挂着**已经收到的响应**(状态行与响应头),
/// 但不读响应体:声明了别的类型就可能是任意大小的二进制,要看原文请改走
/// `Client::stream`。
///
/// 拿到 `SseStream` 之后要负责读到 EOF 或调用 `close()`,否则连接不会释放。
pub async fn Client::sse(
  self : Client,
  config : Config,
  signal? : AbortSignal,
) -> SseStream raise HttpError {
  let (raw, merged) = self.open_stream(config, signal)
  if !@util.declares_event_stream(raw.headers) {
    // 连接已经建立,报错前必须先释放,否则漏一条 TCP 连接。
    raw.body.close()
    raise make_error(
      "响应没有声明 text/event-stream,不能用 Client::sse 按事件读;需要原始字节流请用 Client::stream",
      ErrorCode::NotSupported,
      merged,
      Some(
        build_response(raw.status, raw.status_text, raw.headers, b"", merged),
      ),
    )
  }
  {
    status: raw.status,
    status_text: raw.status_text,
    headers: raw.headers,
    config: merged,
    body: raw.body,
    parser: @sse.SseParser::new(),
    closed: false,
  }
}