// 文件下载的三种形态,共用一条 GET 开流管线(`open_get_stream`):
// - `get_file`:全量读进内存,返回内容与响应元数据;
// - `download_file`:同步分块回调,调用方自己消费字节;
// - `download_to_path`:直接流式写本地文件(写盘是 async,只能在内部做,
//   同步 `on_chunk` 回调里 await 不了,这正是它存在的理由)。
// 三者都支持 `range?`(RFC 7233 区间下载,服务端接受时回 206);
// 断点续传在 download_resume.mbt。上传实现按方向拆到 file_ops.mbt(RL-04)。

///|
/// 下载文件到内存(GET),返回内容与响应元数据
/// (Content-Type / Content-Length / ETag / Last-Modified)。
/// `on_progress` 由 moonhttp 在读响应体过程中触发。
///
/// - `range`:区间下载(RFC 7233)。提供则发 `Range` 头,服务端支持时
///   回 206、`content` 只是区间内容,`content_range` 携带解析结果;
///   服务端忽略 Range 回 200 时返回全量(调用方以 `status` 区分)。
pub async fn WebdavClient::get_file(
  self : WebdavClient,
  path~ : String,
  range? : @types.ByteRange,
  conditions? : @types.Conditions,
  on_progress? : (@types.Progress) -> Unit,
  signal? : @moonhttp.AbortSignal,
) -> @types.GetResult raise @types.WebdavError {
  let url = self.url_for(path)
  let mut cfg = apply_conditions(
    @config.Config::new(url).with_method(@config.Method::Get),
    conditions,
    operation="GET",
    url~,
  )
  match range {
    Some(range) => cfg = cfg.with_header("Range", range.header_value())
    None => ()
  }
  match adapt_progress(on_progress) {
    Some(report) => cfg = cfg.with_on_download_progress(report)
    None => ()
  }
  let response = self.perform(cfg, signal?)
  // 带 Range 的请求允许 206(部分内容);服务端也可能忽略 Range 回 200 全量
  let ok = if range is Some(_) { [200, 206] } else { [200] }
  let checked = check_status(response, operation="GET", ok~)
  {
    content: checked.bytes(),
    status: checked.status,
    content_type: checked.headers.get("Content-Type"),
    content_length: read_content_length(checked.headers),
    etag: checked.headers.get("ETag"),
    last_modified: checked.headers.get("Last-Modified"),
    content_range: parse_content_range_header(checked.headers),
  }
}

///|
/// 流式下载(GET 分块消费):每个分块交给 `on_chunk`(如逐块写文件),
/// 读完即止,内存占用与文件大小无关。
///
/// - `chunk_size`:单次读取上限,默认 64 KiB;
/// - `range`:区间下载(RFC 7233),只消费服务端返回的区间内容;
///   进度按**本次响应**计(`total` 取响应 Content-Length,206 时是区间长度);
/// - `on_progress`:按已接收字节累计触发,`total` 来自 Content-Length
///   (服务端未返回长度时为 `None`);
/// - `on_chunk` 是**同步**回调:只适合内存消费(拼接 Buffer、算哈希、
///   喂同步通道)。要落盘或做别的 async 副作用,用 `download_to_path`。
pub async fn WebdavClient::download_file(
  self : WebdavClient,
  path~ : String,
  on_chunk~ : (Bytes) -> Unit,
  chunk_size? : Int32,
  range? : @types.ByteRange,
  conditions? : @types.Conditions,
  on_progress? : (@types.Progress) -> Unit,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  let size = download_chunk_size(chunk_size)
  let stream = self.open_get_stream(path~, range?, conditions?, signal?)
  let total = read_content_length(stream.headers)
  let mut loaded = 0
  let outcome : Result[Unit, @types.WebdavError] = try {
    while true {
      let chunk = stream.read_some(max_len=size) catch {
        error => raise map_http_error(error, signal_reason(signal))
      }
      match chunk {
        Some(bytes) => {
          on_chunk(bytes)
          loaded = loaded + bytes.length()
          match on_progress {
            Some(report) => report({ loaded, total, })
            None => ()
          }
        }
        None => break
      }
    }
    Ok(())
  } catch {
    error => Err(error)
  }
  stream.close()
  match outcome {
    Ok(_) => ()
    Err(error) => raise error
  }
}

///|
/// 流式下载到本地文件:边收边写盘,内存占用与文件大小无关。
/// 与 `put_file_from_path` 对称,是「下载到磁盘」的正路——
/// 同步 `on_chunk` 回调里没法 await 写文件。
///
/// - `path`:服务端源路径;`local_path`:本地目标文件(已存在则截断覆盖);
/// - `chunk_size`:单次读取 / 写入上限,默认 64 KiB;
/// - `range`:区间下载(RFC 7233),服务端接受时只写区间内容,
///   进度按本次响应计(206 时 `total` 是区间长度);
/// - 顺序上**先发请求、拿到 200 / 206 才建本地文件**:404 等失败不会
///   清空本地已存在的同名文件;
/// - 本地文件创建 / 写入失败抛 `WebdavError::Io`;读流失败经统一映射
///   (取消 → `Cancelled`,其余 → `Http`);
/// - 失败或取消时可能留下**部分内容**的本地文件(不自动删除)——
///   需要从中断处继续时用 `download_to_path_resume`;
/// - `on_progress`:按已写入字节累计触发,`total` 来自 Content-Length
///   (服务端未返回长度时为 `None`)。
pub async fn WebdavClient::download_to_path(
  self : WebdavClient,
  path~ : String,
  local_path~ : String,
  chunk_size? : Int32,
  range? : @types.ByteRange,
  conditions? : @types.Conditions,
  on_progress? : (@types.Progress) -> Unit,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  let size = download_chunk_size(chunk_size)
  let stream = self.open_get_stream(path~, range?, conditions?, signal?)
  let total = read_content_length(stream.headers)
  write_stream_to_file(
    stream,
    local_path~,
    append=false,
    offset=0,
    total~,
    chunk_size=size,
    on_progress?,
    signal?,
  )
}

///|
/// 打开 GET 流:组装请求、发流式请求、校验状态码。带 `range` 时允许
/// 206(部分内容,服务端也可能忽略 Range 回 200 全量),否则维持
/// 「非 200 即错」;失败先关闭流再抛 `Api`(不把连接漏在那儿),
/// 是两条流式下载路径的公共前缀。
async fn WebdavClient::open_get_stream(
  self : WebdavClient,
  path~ : String,
  range? : @types.ByteRange,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> @moonhttp.StreamResponse raise @types.WebdavError {
  let url = self.url_for(path)
  let mut cfg = apply_conditions(
    @config.Config::new(url).with_method(@config.Method::Get),
    conditions,
    operation="GET",
    url~,
  )
  match range {
    Some(range) => cfg = cfg.with_header("Range", range.header_value())
    None => ()
  }
  let stream = self.http.stream(cfg, signal?) catch {
    error => raise map_http_error(error, signal_reason(signal))
  }
  let ok = if range is Some(_) { [200, 206] } else { [200] }
  if !ok.contains(stream.status) {
    let status = stream.status
    stream.close()
    raise @types.WebdavError::api(
      status~,
      operation="GET",
      message=status_message(status),
    )
  }
  stream
}

///|
/// 把 GET 响应流写进本地文件:`append=false` 截断覆盖(200 全量),
/// `append=true` 追加续写(206 续传)。进度按 `offset` 起累计到
/// `total`。流与文件在收尾时总是成对关闭,读流失败经统一映射。
async fn write_stream_to_file(
  stream : @moonhttp.StreamResponse,
  local_path~ : String,
  append~ : Bool,
  offset~ : Int32,
  total~ : Int32?,
  chunk_size~ : Int32,
  on_progress? : (@types.Progress) -> Unit,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  let file = if append {
    @fs.open(
      local_path,
      mode=@fs.Mode::WriteOnly,
      append=true,
      create_mode=@fs.CreateMode::OpenOrCreate,
    ) catch {
      error => {
        stream.close()
        raise @types.WebdavError::io(
          message="打开本地文件续写失败(\{local_path}):\{error}",
        )
      }
    }
  } else {
    @fs.create(local_path, allow_existing=true) catch {
      error => {
        stream.close()
        raise @types.WebdavError::io(
          message="创建本地文件失败(\{local_path}):\{error}",
        )
      }
    }
  }
  let mut loaded = offset
  let outcome : Result[Unit, @types.WebdavError] = try {
    while true {
      let chunk = stream.read_some(max_len=chunk_size) catch {
        error => raise map_http_error(error, signal_reason(signal))
      }
      match chunk {
        Some(bytes) => {
          file.write(bytes) catch {
            error =>
              raise @types.WebdavError::io(
                message="写入本地文件失败(\{local_path}):\{error}",
              )
          }
          loaded = loaded + bytes.length()
          match on_progress {
            Some(report) => report({ loaded, total, })
            None => ()
          }
        }
        None => break
      }
    }
    Ok(())
  } catch {
    error => Err(error)
  }
  file.close()
  stream.close()
  match outcome {
    Ok(_) => ()
    Err(error) => raise error
  }
}

///|
/// 206 响应的 `Content-Range` 解析:缺失或形态不符为 `None`。
fn parse_content_range_header(
  headers : @moonhttp.Headers,
) -> @types.ContentRange? {
  match headers.get("Content-Range") {
    Some(text) => @types.parse_content_range(text)
    None => None
  }
}

///|
/// 流式下载的分块大小:`None` / 非正数一律取默认 64 KiB。
fn download_chunk_size(chunk_size : Int32?) -> Int32 {
  match chunk_size {
    Some(value) if value > 0 => value
    _ => 64 * 1024
  }
}

///|
/// 从响应头取 Content-Length(缺失或非数字时为 `None`)。
fn read_content_length(headers : @moonhttp.Headers) -> Int32? {
  match headers.get("Content-Length") {
    Some(text) => @types.parse_ascii_int(text)
    None => None
  }
}