// 目录与属性操作:MKCOL / DELETE / PROPFIND(list / stat / get_properties)
// 与 PROPPATCH(set_properties)。
// 存在性判断与能力探测见 probe_ops.mbt。

///|
/// 自定义属性(死属性)的默认命名空间。
pub const DEFAULT_PROP_NS : String = "urn:q2316367743:webdav:prop"

///|
/// 创建目录(MKCOL)。单级创建:父目录不存在时服务端通常返回 409,
/// 需要 `mkdir -p` 语义时用 `mkdir_all`(见 mkdir_all.mbt)。
/// `conditions` 可用于 `If-None-Match: *`(父目录已存在时不要覆盖)
/// 或携带锁令牌。
pub async fn WebdavClient::mkdir(
  self : WebdavClient,
  path~ : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  ignore(
    self.send(
      operation="MKCOL",
      http_method=@config.Method::Other("MKCOL"),
      path~,
      conditions?,
      ok=[201],
      signal?,
    ),
  )
}

///|
/// 删除文件或目录(DELETE,目录要求为空或服务端支持递归)。
///
/// 放行 207:部分服务端对批量 / 递归删除返回 Multi-Status,
/// 只要有资源没删成功就抛 `Partial`(`failures` 列出失败条目)。
pub async fn WebdavClient::delete(
  self : WebdavClient,
  path~ : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  let response = self.send(
    operation="DELETE",
    http_method=@config.Method::Delete,
    path~,
    conditions?,
    signal?,
    ok=[200, 204, 207],
  )
  check_status_allow_partial(response, operation="DELETE", ok=[200, 204, 207])
}

///|
/// 列目录(PROPFIND allprop):默认 `Depth:1` 返回直接子项,不含目录自身。
///
/// - `depth`:`One`(默认,直接子项)/ `Infinity`(整棵子树,见
///   `list_recursive`)/ `Zero`(只有目录自身,结果通常为空);
/// - href 与服务端路径的对齐统一走 `@types.href_to_path`
///   (剥绝对 URL 前缀、丢 query / fragment、percent 解码、去尾斜杠)。
pub async fn WebdavClient::list(
  self : WebdavClient,
  path~ : String,
  depth? : @types.Depth,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Array[@types.FileInfo] raise @types.WebdavError {
  let resources = self.propfind(
    path~,
    depth=depth.unwrap_or(@types.Depth::One),
    conditions?,
    signal?,
  )
  let server_path = base_path_prefix(self.base_url) +
    (if path.has_prefix("/") { path } else { "/" + path })
  let items : Array[@types.FileInfo] = []
  for resource in resources {
    if !same_resource(resource.href, server_path) {
      items.push(resource.to_file_info())
    }
  }
  items
}

///|
/// 递归列出整棵子树(PROPFIND `Depth:infinity`)。
/// 服务端可能限制深度并返回 403,调用方可自行降级为逐层 `list`。
pub async fn WebdavClient::list_recursive(
  self : WebdavClient,
  path~ : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Array[@types.FileInfo] raise @types.WebdavError {
  self.list(path~, depth=@types.Depth::Infinity, conditions?, signal?)
}

///|
/// 获取单个资源的属性(PROPFIND `Depth:0` allprop)。
/// 资源不存在时服务端返回 404(抛 `Api` 错误)。
pub async fn WebdavClient::stat(
  self : WebdavClient,
  path~ : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> @types.FileInfo raise @types.WebdavError {
  let resources = self.propfind(
    path~,
    depth=@types.Depth::Zero,
    conditions?,
    signal?,
  )
  if resources.length() == 0 {
    raise @types.WebdavError::api(
      status=207,
      operation="PROPFIND",
      message="响应中没有资源条目",
    )
  }
  resources[0].to_file_info()
}

///|
/// 写入自定义属性(PROPPATCH set)。
///
/// - `props`:(属性名, 值) 列表;
/// - `ns_uri`:属性命名空间,默认 `DEFAULT_PROP_NS`;
///   属性名需为合法 XML 名(非法时抛 `Api` 400)。
pub async fn WebdavClient::set_properties(
  self : WebdavClient,
  path~ : String,
  props~ : Array[(String, String)],
  ns_uri? : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Unit raise @types.WebdavError {
  let body = @xmlutil.proppatch_set_body(
    props,
    ns_uri=ns_uri.unwrap_or(DEFAULT_PROP_NS),
  ) catch {
    error => raise invalid_request_body(error, operation="PROPPATCH")
  }
  ignore(
    self.send(
      operation="PROPPATCH",
      http_method=@config.Method::Other("PROPPATCH"),
      path~,
      headers=[("Content-Type", "application/xml; charset=utf-8")],
      body~,
      conditions?,
      signal?,
      ok=[207],
    ),
  )
}

///|
/// 读取自定义属性(PROPFIND 按名请求):返回 属性名 → 值,
/// 服务端没有的属性(404 propstat)不出现在结果中。
/// 需要区分「没有该属性」与「值为空」时用 `get_properties_with_status`。
pub async fn WebdavClient::get_properties(
  self : WebdavClient,
  path~ : String,
  names~ : Array[String],
  ns_uri? : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Map[String, String] raise @types.WebdavError {
  let results = self.get_properties_with_status(
    path~,
    names~,
    ns_uri?,
    conditions?,
    signal?,
  )
  let map : Map[String, String] = Map([])
  for prop in results {
    if prop.status >= 200 && prop.status < 300 {
      map.set(prop.name, prop.value)
    }
  }
  map
}

///|
/// 读取自定义属性并保留每个属性的状态码:404 表示服务端没有这个属性,
/// 与「属性值为空串」区分开(部分服务端对未存储的死属性回 404 propstat)。
pub async fn WebdavClient::get_properties_with_status(
  self : WebdavClient,
  path~ : String,
  names~ : Array[String],
  ns_uri? : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Array[@types.PropResult] raise @types.WebdavError {
  let ns = ns_uri.unwrap_or(DEFAULT_PROP_NS)
  let body = @xmlutil.propfind_named_body(names, ns_uri=ns) catch {
    error => raise invalid_request_body(error, operation="PROPFIND")
  }
  let resources = self.send_propfind(
    path~,
    depth=@types.Depth::Zero,
    body~,
    conditions?,
    signal?,
  )
  let results : Array[@types.PropResult] = []
  if resources.length() > 0 {
    for prop in resources[0].props {
      if prop.ns_uri == Some(ns) {
        results.push({
          name: prop.name,
          value: prop.value,
          status: prop.status,
        })
      }
    }
  }
  results
}

///|
/// 发送 allprop 的 PROPFIND 并解析 207 响应。
async fn WebdavClient::propfind(
  self : WebdavClient,
  path~ : String,
  depth~ : @types.Depth,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Array[@xmlutil.ParsedResource] raise @types.WebdavError {
  self.send_propfind(
    path~,
    depth~,
    body=@xmlutil.propfind_allprop_body(),
    conditions?,
    signal?,
  )
}

///|
/// 发送 PROPFIND(指定请求体)并解析 207 响应。
async fn WebdavClient::send_propfind(
  self : WebdavClient,
  path~ : String,
  depth~ : @types.Depth,
  body~ : String,
  conditions? : @types.Conditions,
  signal? : @moonhttp.AbortSignal,
) -> Array[@xmlutil.ParsedResource] raise @types.WebdavError {
  let response = self.send(
    operation="PROPFIND",
    http_method=@config.Method::Other("PROPFIND"),
    path~,
    headers=[
      ("Depth", depth.to_header()),
      ("Content-Type", "application/xml; charset=utf-8"),
    ],
    body~,
    conditions?,
    signal?,
    ok=[207],
  )
  @xmlutil.parse_multistatus(response.text()) catch {
    error => raise @types.WebdavError::xml(error)
  }
}

///|
/// href 与请求的服务器路径是否指向同一资源(归一后比较)。
fn same_resource(href : String, server_path : String) -> Bool {
  @types.href_to_path(href) == @types.href_to_path(server_path)
}

///|
/// 请求体 XML 生成失败的统一映射:属性名 / 属性值 / 结构非法(400)。
/// `operation` 是发起该请求的操作名,用于错误文本。
fn invalid_request_body(
  error : @xml.WriterError,
  operation~ : String,
) -> @types.WebdavError {
  let detail = match error {
    InvalidName(_) => "属性名不构成合法 XML 名"
    InvalidContent(_) => "属性值包含非法内容"
    InvalidStructure(_) => "XML 结构非法"
    InvalidDeclaration(_) => "XML 声明非法"
  }
  @types.WebdavError::api(status=400, operation~, message=detail)
}