///|
/// Join a base URL and a possibly relative URL to form an absolute
/// interpretation of the latter, exactly like Python's
/// `urllib.parse.urljoin(base, url, allow_fragments=True)`.
///
/// This mirrors Python's quirks: relative resolution only happens when the
/// scheme of `url` (defaulting to the base's) equals the base's scheme *and*
/// is listed in `uses_relative` (so e.g. `urn:` and `tag:` bases are never
/// joined against), the network location is only inherited for schemes in
/// `uses_netloc`, and excess `..` segments are dropped (RFC 3986).
///
/// ```mbt check
/// test {
///   inspect(
///     @urllib.urljoin("http://example.com/a/b", "../c"),
///     content="http://example.com/c",
///   )
///   inspect(@urllib.urljoin("urn:example:foo", "#bar"), content="#bar")
/// }
/// ```
pub fn urljoin(
  base : String,
  url : String,
  allow_fragments? : Bool = true,
) -> String raise ValueError {
  if base == "" {
    return url
  }
  if url == "" {
    return base
  }
  let b = urlparse(base, scheme="", allow_fragments~)
  let u = urlparse(url, scheme=b.scheme, allow_fragments~)
  let scheme = u.scheme
  let mut netloc = u.netloc
  let mut path = u.path
  let mut params = u.params
  let mut query = u.query
  let fragment = u.fragment
  if scheme != b.scheme || !in_list(uses_relative, scheme) {
    return url
  }
  if in_list(uses_netloc, scheme) {
    if netloc != "" {
      return urlunparse(scheme, netloc, path, params, query, fragment)
    }
    netloc = b.netloc
  }
  if path == "" && params == "" {
    path = b.path
    params = b.params
    if query == "" {
      query = b.query
    }
    return urlunparse(scheme, netloc, path, params, query, fragment)
  }
  let base_parts = split_all(b.path, "/")
  if base_parts[base_parts.length() - 1] != "" {
    // the last item is not a directory, so will not be taken into account
    // in resolving the relative path
    base_parts.pop() |> ignore
  }
  let segments = if path.has_prefix("/") {
    split_all(path, "/")
  } else {
    let all = base_parts
    all.append(split_all(path, "/"))
    // filter out elements that would cause redundant slashes on re-joining
    // the resolved_path
    if all.length() > 2 {
      let middle = all[1:all.length() - 1]
        .iter()
        .filter(s => s != "")
        .to_array()
      [all[0], ..middle, all[all.length() - 1]]
    } else {
      all
    }
  }
  let resolved_path : Array[String] = []
  for seg in segments {
    if seg == ".." {
      resolved_path.pop() |> ignore
    } else if seg == "." {
      continue
    } else {
      resolved_path.push(seg)
    }
  }
  let last = segments[segments.length() - 1]
  if last == "." || last == ".." {
    // if the last segment was a relative dir, then we need to append the
    // trailing '/'
    resolved_path.push("")
  }
  let joined = resolved_path.join("/")
  path = if joined == "" { "/" } else { joined }
  urlunparse(scheme, netloc, path, params, query, fragment)
}