// 把 `PreparedRequest.url` 切成「拨号要的地址」与「请求行要的目标」。
//
// 切法(`://` 之后第一个 `/`):自研栈要自己建连、
// 自己拼 Host,所以多拆出 host 与端口(含按 scheme 补默认值)。

///|
/// 一个可以拨号的请求目标。
pub(all) struct Target {
  /// 小写 scheme:`http` / `https` / `socks5`(socks5 只为代理地址解析而认识,
  /// 作为请求目标会被上层拒绝)
  scheme : String
  /// 是否在连接上叠 TLS
  use_tls : Bool
  /// 主机名(CONNECT 的隧道地址、TLS 的 SNI、`Host` 头的主机段都用它)
  host : String
  /// 端口;URL 里没写时按 scheme 补默认值(http 80 / https 443)
  port : Int32
  /// 请求目标(origin-form,即 path?query);URL 没有路径时为 `/`
  target : String
} derive(Debug)

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

///|
/// 解析绝对 URL。相对地址与非 http(s)/socks5 协议抛 `Unsupported`:
/// 它们的共同点是「请求还没发出去就失败了」,正是这个分类的定义。
pub fn parse_target(url : String) -> Target raise @transport.TransportError {
  let scheme_end = match url.find("://") {
    Some(index) => index
    None =>
      raise @transport.TransportError::Unsupported(
        "地址不是绝对地址:" + url,
      )
  }
  let scheme = url[:scheme_end].to_owned().to_lower()
  let use_tls = match scheme {
    "http" => false
    "https" => true
    "socks5" => false
    _ =>
      raise @transport.TransportError::Unsupported(
        "只支持 http/https 协议:" + scheme,
      )
  }
  let rest = url[scheme_end + 3:]
  // 切不开路径时(例如 `https://host`)按 HTTP 约定补成 `/`
  let (authority, target) = match rest.find("/") {
    Some(index) => (rest[:index], rest[index:].to_owned())
    None => (rest, "/")
  }
  let (host, port) = split_authority(authority, default_port(scheme))
  { scheme, use_tls, host, port, target, }
}

///|
/// scheme 的默认端口(http 80 / https 443;socks5 也按 1080 记,
/// 它只出现在代理地址上)。
fn default_port(scheme : String) -> Int32 {
  match scheme {
    "https" => 443
    "socks5" => 1080
    _ => 80
  }
}

///|
/// 把 `host[:port]` 切开。端口段必须全是数字且在 1..=65535,否则抛
/// `Unsupported`——发不出去的地址,不值得建连去试。
///
/// IPv6 字面量只认方括号形式(`[::1]:8080` 或 `[::1]`):切分只认**方括号外**
/// 的最后一个冒号,裸冒号形式与「host:port」天然冲突,也不在常见写法里。
fn split_authority(
  authority : StringView,
  default_port : Int32,
) -> (String, Int32) raise @transport.TransportError {
  if authority.length() == 0 {
    raise @transport.TransportError::Unsupported("地址缺少主机名")
  }
  // 找方括号之外的最后一个冒号(即 host:port 的分隔符)
  let mut colon : Int32? = None
  let mut in_brackets = false
  for i in 0.. (authority.to_owned(), default_port)
    Some(index) => {
      let port_part = authority[index + 1:]
      if port_part.length() == 0 {
        return (authority.to_owned(), default_port)
      }
      let mut port = 0
      for i in 0.. 57 {
          raise @transport.TransportError::Unsupported(
            "端口不是数字:{authority}",
          )
        }
        port = port * 10 + (code - 48)
      }
      if port < 1 || port > 65535 {
        raise @transport.TransportError::Unsupported(
          "端口越界:{authority}",
        )
      }
      (authority[:index].to_owned(), port)
    }
  }
}

///|
/// `Host` 头的值:默认端口按 RFC 9112 §7.2 省略,非默认端口必须带上。
pub fn host_header(target : Target) -> String {
  let default = default_port(target.scheme)
  if target.port == default {
    target.host
  } else {
    "\{target.host}:\{target.port}"
  }
}