// The parts of a URL an OAuth client has to take apart: RFC 9728 and RFC 8414
// both hang a document off the authority or off the path, and the endpoints
// they name are then called with a query this builds.

///|
/// Everything in `url` before its path: scheme, authority, and nothing else.
pub fn origin_of(url : String) -> String {
  match url.find("://") {
    Some(colon) =>
      match url.exact_view(start=colon + 3).to_owned().find("/") {
        Some(slash) => url.exact_view(end=colon + 3 + slash).to_owned()
        None => url
      }
    None => url
  }
}

///|
/// The path of `url`, from the slash after the authority up to the query or
/// fragment. `/` when it has none, which is what the well-known lookups expect.
pub fn path_of(url : String) -> String {
  let rest = match url.find("://") {
    Some(colon) => url.exact_view(start=colon + 3).to_owned()
    None => url
  }
  let from_authority = match rest.find("/") {
    Some(slash) => rest.exact_view(start=slash).to_owned()
    None => return "/"
  }
  let path = match from_authority.find("?") {
    Some(at) => from_authority.exact_view(end=at).to_owned()
    None => from_authority
  }
  if path.is_empty() {
    "/"
  } else {
    path
  }
}

///|
/// The path of `url` without its trailing slash, which is the form the
/// well-known prefixes are built from.
pub fn trimmed_path_of(url : String) -> String {
  let path = path_of(url)
  if path == "/" {
    ""
  } else if path.has_suffix("/") {
    path.exact_view(end=path.length() - 1).to_owned()
  } else {
    path
  }
}

///|
/// `url` with `query` as its query, replacing one it had.
pub fn with_query(url : String, query : Array[(String, String)]) -> String {
  let base = match url.find("?") {
    Some(at) => url.exact_view(end=at).to_owned()
    None => url
  }
  if query.is_empty() {
    return base
  }
  let pairs = query.map(pair => {
    let (name, value) = pair
    "\{@moonurl.escape_text(name, kind=@moonurl.Query)}=\{@moonurl.escape_text(value, kind=@moonurl.Query)}"
  })
  base + "?" + pairs.join("&")
}

///|
/// The whole of `url` as a request line carries it: path and query.
pub fn target_of(url : String) -> String {
  let origin = origin_of(url)
  if origin.length() >= url.length() {
    "/"
  } else {
    url.exact_view(start=origin.length()).to_owned()
  }
}

///|
/// The value of `name` in a query string, with `+` read as a space as
/// `application/x-www-form-urlencoded` says and the escapes decoded.
pub fn query_value(query : String, name : String) -> String? {
  for pair in query.split("&") {
    let (key, value) = match pair.find("=") {
      Some(at) =>
        (
          pair.exact_view(end=at).to_owned(),
          pair.exact_view(start=at + 1).to_owned(),
        )
      None => (pair.to_owned(), "")
    }
    if key == name {
      let spaced = value.replace_all(old="+", new=" ")
      return Some(@moonurl.unescape_text(spaced))
    }
  }
  None
}

///|
/// The query of `url`, without the `?`.
pub fn query_of(url : String) -> String {
  match url.find("?") {
    Some(at) => url.exact_view(start=at + 1).to_owned()
    None => ""
  }
}

///|
/// Whether `url` names the same resource as `expected`: the scheme and host
/// compared without regard to case, the rest exactly. RFC 8707 lets a client
/// refuse a protected resource that names another origin.
pub fn same_origin(url : String, expected : String) -> Bool {
  origin_of(url).to_lower() == origin_of(expected).to_lower()
}