/// URI component parsing (scheme, query, fragment)

///|
/// Find first occurrence of character in string
fn find_char_in_str(s : String, c : Char) -> Int? {
  let chars = s.to_array()
  for i = 0; i < chars.length(); i = i + 1 {
    if chars[i] == c {
      return Some(i)
    }
  }
  None
}

///|
/// Parse scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
pub fn parse_scheme(s : String) -> URIResult[String] {
  if s.is_empty() {
    Err(InvalidScheme("Scheme cannot be empty"))
  } else {
    let chars = s.to_array()

    // First character must be ALPHA
    if !is_alpha(chars[0]) {
      Err(InvalidScheme("Scheme must start with a letter"))
    } else {
      // Check remaining characters
      let mut valid = true
      for i = 1; i < chars.length(); i = i + 1 {
        if !is_scheme_char(chars[i]) {
          valid = false
        }
      }
      if valid {
        Ok(s.to_lower()) // Schemes are case-insensitive, normalize to lowercase
      } else {
        Err(InvalidScheme("Invalid characters in scheme"))
      }
    }
  }
}

///|
/// Parse query: *( pchar / "/" / "?" )
pub fn parse_query(s : String) -> URIResult[String] {
  if is_valid_encoded_string(s, is_query_char) {
    Ok(s)
  } else {
    Err(InvalidQuery("Invalid characters in query"))
  }
}

///|
/// Parse fragment: *( pchar / "/" / "?" )
pub fn parse_fragment(s : String) -> URIResult[String] {
  if is_valid_encoded_string(s, is_fragment_char) {
    Ok(s)
  } else {
    Err(InvalidFragment("Invalid characters in fragment"))
  }
}

///|
/// Split query string into key-value pairs.
/// Keys without '=' map to None; "key=" maps to Some("").
pub fn parse_query_params(query : String) -> Array[(String, String?)] {
  if query.is_empty() {
    return []
  }
  let pairs = query.split("&")
  let result : Array[(String, String?)] = []
  for pair in pairs {
    let pair_str = pair.to_owned()
    if pair_str.contains("=") {
      let eq_pos = find_char_in_str(pair_str, '=')
      match eq_pos {
        Some(pos) => {
          let key = pair_str[0:pos].to_owned()
          let value = pair_str[pos + 1:].to_owned()
          // Decode percent-encoded values
          match (percent_decode(key), percent_decode(value)) {
            (Ok(decoded_key), Ok(decoded_value)) =>
              result.push((decoded_key, Some(decoded_value)))
            _ =>
              // If decoding fails, keep original values
              result.push((key, Some(value)))
          }
        }
        None => () // Should not happen since we checked contains
      }
    } else {
      // Key without value
      match percent_decode(pair_str) {
        Ok(decoded_key) => result.push((decoded_key, None))
        Err(_) => result.push((pair_str, None))
      }
    }
  }
  result
}

///|
/// Parse and validate a raw query string into a map of key-value pairs.
/// Keys without '=' map to None; an empty/absent query yields an empty map.
pub fn parse_query_map(query : String) -> URIResult[Map[String, String?]] {
  match parse_query(query) {
    Err(e) => Err(e)
    Ok(q) => {
      let map : Map[String, String?] = Map([])
      for pair in parse_query_params(q) {
        let (key, value) = pair
        map.set(key, value)
      }
      Ok(map)
    }
  }
}

///|
/// Get the value of a query parameter from a URI.
/// Returns Some(value) when the key exists with a value (Some("") for "key="),
/// None for a bare key without a value, and None when the key is absent
/// or the URI has no query at all.
pub fn URI::get_query(self : URI, key : String) -> String? {
  match self.query.get(key) {
    Some(value) => value
    None => None
  }
}

///|
/// Escape the query-string delimiters '&' and '=' inside an encoded key or
/// value, so they cannot be mistaken for parameter separators.
fn escape_query_delimiters(s : String) -> String {
  let buf = StringBuilder()
  for c in s.to_array() {
    match c {
      '&' => buf.write_string("%26")
      '=' => buf.write_string("%3D")
      _ => buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
/// Build query string from key-value pairs.
/// None values render as a bare key; Some("") renders as "key=".
pub fn build_query_string(params : Array[(String, String?)]) -> String {
  if params.is_empty() {
    return ""
  }
  let parts : Array[String] = []
  for i = 0; i < params.length(); i = i + 1 {
    let (key, value) = params[i]
    let encoded_key = escape_query_delimiters(encode_query(key))
    match value {
      None => parts.push(encoded_key)
      Some(v) => {
        let encoded_value = escape_query_delimiters(encode_query(v))
        parts.push(encoded_key + "=" + encoded_value)
      }
    }
  }
  parts.join("&")
}

///|
/// Validate that all URI components are properly formed
pub fn validate_uri_components(
  scheme : String,
  authority : Authority?,
  path : String,
  query : Map[String, String?],
  fragment : String?,
) -> URIResult[Unit] {
  // Validate scheme
  match parse_scheme(scheme) {
    Ok(_) => ()
    Err(e) => return Err(e)
  }

  // Query keys/values are stored decoded; the raw query string is
  // validated at parse time (see parse_query_map), so there is nothing
  // further to validate here.
  ignore(query)

  // Validate fragment if present
  match fragment {
    Some(f) =>
      match parse_fragment(f) {
        Ok(_) => ()
        Err(e) => return Err(e)
      }
    None => ()
  }

  // Authority validation is done during parsing

  // Path validation depends on whether authority is present
  match authority {
    Some(_) =>
      // With authority, path must be path-abempty
      match validate_path_abempty(path) {
        Ok(_) => Ok(())
        Err(e) => Err(e)
      }
    None =>
      // Without authority, validate based on path type
      match classify_and_validate_path(path) {
        Ok(_) => Ok(())
        Err(e) => Err(e)
      }
  }
}

///|
/// Case-insensitive scheme comparison
pub fn schemes_equal(scheme1 : String, scheme2 : String) -> Bool {
  scheme1.to_lower() == scheme2.to_lower()
}

///|
/// Normalize URI component case (schemes and host names are case-insensitive)
pub fn normalize_uri_case(uri : URI) -> URI {
  let normalized_authority = match uri.authority {
    Some(auth) => {
      let normalized_host = match auth.host {
        HostType::RegName(name) => HostType::RegName(name.to_lower())
        other => other
      }
      Some({ ..auth, host: normalized_host, })
    }
    None => None
  }
  { ..uri, scheme: uri.scheme.to_lower(), authority: normalized_authority, }
}