///|
/// Result of the intentionally small RFC 3986 URI-reference syntax check.
pub struct UriReferenceInfo {
  absolute : Bool
  relative : Bool
} derive(Eq, Debug)

///|
pub fn UriReferenceInfo::is_absolute(self : UriReferenceInfo) -> Bool {
  self.absolute
}

///|
pub fn UriReferenceInfo::is_relative(self : UriReferenceInfo) -> Bool {
  self.relative
}

///|
/// Validate the syntax needed by RFC 9457 without resolving or dereferencing.
pub fn validate_uri_reference(
  value : String,
) -> Result[UriReferenceInfo, ProblemError] {
  let chars = value.to_array()
  let mut colon = -1
  let mut boundary = chars.length()
  for i = 0; i < chars.length(); i = i + 1 {
    let code = chars[i].to_int()
    if code <= 0x20 || code == 0x7F {
      return Err(
        problem_error(
          Uri,
          InvalidUriReference,
          "URI-reference contains space or control character",
        ),
      )
    }
    if chars[i] == '%' {
      if i + 2 >= chars.length() ||
        !is_hex(chars[i + 1]) ||
        !is_hex(chars[i + 2]) {
        return Err(
          problem_error(
            Uri,
            InvalidPercentEncoding,
            "malformed percent encoding",
          ),
        )
      }
    }
    if chars[i] == '/' || chars[i] == '?' || chars[i] == '#' {
      if boundary == chars.length() {
        boundary = i
      }
    } else if chars[i] == ':' && colon < 0 {
      colon = i
    }
  }
  let absolute = colon >= 0 && colon < boundary
  if absolute && !valid_scheme(chars, colon) {
    return Err(
      problem_error(Uri, InvalidUriReference, "invalid URI scheme syntax"),
    )
  }
  Ok({ absolute, relative: !absolute })
}

///|
pub fn is_absolute_uri_reference(value : String) -> Bool {
  match validate_uri_reference(value) {
    Ok(info) => info.is_absolute()
    Err(_) => false
  }
}

///|
fn valid_scheme(chars : Array[Char], colon : Int) -> Bool {
  if colon <= 0 || !is_alpha(chars[0]) {
    return false
  }
  for i = 1; i < colon; i = i + 1 {
    let c = chars[i]
    if !is_alpha(c) && !is_digit(c) && c != '+' && c != '-' && c != '.' {
      return false
    }
  }
  true
}

///|
fn is_alpha(c : Char) -> Bool {
  (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
}

///|
fn is_digit(c : Char) -> Bool {
  c >= '0' && c <= '9'
}

///|
fn is_hex(c : Char) -> Bool {
  is_digit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')
}