/// Percent-encoding and decoding utilities based on RFC 3986

///| Check if a string contains valid percent-encoding
/// pct-encoded = "%" HEXDIG HEXDIG
pub fn is_valid_percent_encoding(s : String) -> Bool {
  let chars = s.to_array()
  let len = chars.length()
  fn check_from(i : Int) -> Bool {
    if i >= len {
      true
    } else if chars[i] == '%' {
      if i + 2 < len && is_hexdig(chars[i + 1]) && is_hexdig(chars[i + 2]) {
        check_from(i + 3)
      } else {
        false
      }
    } else {
      check_from(i + 1)
    }
  }

  check_from(0)
}

///| Decode a percent-encoded string
pub fn percent_decode(s : String) -> URIResult[String] {
  let chars = s.to_array()
  let len = chars.length()
  let mut result = ""
  fn decode_from(i : Int) -> URIResult[Unit] {
    if i >= len {
      Ok(())
    } else if chars[i] == '%' {
      if i + 2 < len {
        match (hex_to_int(chars[i + 1]), hex_to_int(chars[i + 2])) {
          (Some(h1), Some(h2)) => {
            let byte_val = h1 * 16 + h2
            // Convert byte value back to character
            // Note: This is a simplified approach for ASCII characters
            if byte_val >= 0 && byte_val <= 127 {
              result = result + byte_val.unsafe_to_char().to_string()
              decode_from(i + 3)
            } else {
              Err(InvalidPercentEncoding("Invalid byte value: \{byte_val}"))
            }
          }
          _ =>
            Err(InvalidPercentEncoding("Invalid hex digits at position \{i}"))
        }
      } else {
        Err(InvalidPercentEncoding("Incomplete percent encoding at end"))
      }
    } else {
      result = result + chars[i].to_string()
      decode_from(i + 1)
    }
  }

  match decode_from(0) {
    Ok(_) => Ok(result)
    Err(e) => Err(e)
  }
}

///| Encode a string with percent-encoding for characters that need it
/// The predicate function determines which characters should be encoded
pub fn percent_encode(s : String, should_encode : (Char) -> Bool) -> String {
  let chars = s.to_array()
  let mut result = ""
  for i = 0; i < chars.length(); i = i + 1 {
    let c = chars[i]
    if should_encode(c) {
      let byte_val = Char::to_int(c)
      if byte_val <= 255 {
        let h1 = byte_val / 16
        let h2 = byte_val % 16
        result = result + "%"
        match int_to_hex(h1) {
          Some(ch) => {
            let upper_ch = if ch >= 'a' && ch <= 'f' {
              (Char::to_int(ch) - 32).unsafe_to_char() // Convert to uppercase
            } else {
              ch
            }
            result = result + upper_ch.to_string()
          }
          None => continue // Should not happen for valid bytes
        }
        match int_to_hex(h2) {
          Some(ch) => {
            let upper_ch = if ch >= 'a' && ch <= 'f' {
              (Char::to_int(ch) - 32).unsafe_to_char() // Convert to uppercase  
            } else {
              ch
            }
            result = result + upper_ch.to_string()
          }
          None => continue // Should not happen for valid bytes
        }
      } else {
        // For non-ASCII characters, we'll just include them as-is
        // In a complete implementation, you'd want proper UTF-8 encoding
        result = result + c.to_string()
      }
    } else {
      result = result + c.to_string()
    }
  }
  result
}

///| Encode a string for use in URI path segments
pub fn encode_path_segment(s : String) -> String {
  percent_encode(s, fn(c) { not(is_pchar(c)) && c != '%' })
}

///| Encode a string for use in URI query
pub fn encode_query(s : String) -> String {
  percent_encode(s, fn(c) { not(is_query_char(c)) && c != '%' })
}

///| Encode a string for use in URI fragment
pub fn encode_fragment(s : String) -> String {
  percent_encode(s, fn(c) { not(is_fragment_char(c)) && c != '%' })
}

///| Encode a string for use in URI userinfo
pub fn encode_userinfo(s : String) -> String {
  percent_encode(s, fn(c) { not(is_userinfo_char(c)) && c != '%' })
}

///| Encode a string for use in URI reg-name (domain names)
pub fn encode_reg_name(s : String) -> String {
  percent_encode(s, fn(c) { not(is_reg_name_char(c)) && c != '%' })
}

///| Check if a string is already properly percent-encoded and contains only valid characters
pub fn is_valid_encoded_string(
  s : String,
  char_validator : (Char) -> Bool
) -> Bool {
  let chars = s.to_array()
  let len = chars.length()
  fn check_from(i : Int) -> Bool {
    if i >= len {
      true
    } else if chars[i] == '%' {
      if i + 2 < len && is_hexdig(chars[i + 1]) && is_hexdig(chars[i + 2]) {
        check_from(i + 3)
      } else {
        false
      }
    } else if char_validator(chars[i]) {
      check_from(i + 1)
    } else {
      false
    }
  }

  check_from(0)
}