///|
/// Represents a parsed URL per the WHATWG URL Standard. See:
/// https://url.spec.whatwg.org/#url-representation
///
/// Contains scheme, credentials (username/password), host, port, path, query,
/// and fragment.
pub struct Url {
  mut scheme : String
  mut username : String
  mut password : String
  mut host : Host?
  mut port : UInt16?
  mut path : Path
  mut query : String?
  mut fragment : String?
}

///|
/// Create a new empty URL
fn Url::new() -> Url {
  {
    scheme: "",
    username: "",
    password: "",
    host: None,
    port: None,
    path: Segments([]),
    query: None,
    fragment: None,
  }
}

///|
/// Parse a URL string, optionally with a base URL for relative resolution
#as_free_fn
pub fn Url::try_parse(
  input : StringView,
  base? : Url,
  validation_errors? : Array[ValidationError],
) -> Url? {
  let url = Url::new()
  Url::parse_basic(url, input, base?, validation_errors?)
}

///|
pub suberror ValidationErrors {
  ValidationErrors(Array[ValidationError])
} derive(Debug)

///|
pub impl Show for ValidationErrors with fn output(self, logger) {
  match self {
    ValidationErrors(errors) => {
      logger.write_string("ValidationErrors([")
      for index, error in errors {
        if index > 0 {
          logger.write_string(", ")
        }
        error.output(logger)
      }
      logger.write_string("])")
    }
  }
}

///|
#as_free_fn
pub fn Url::parse(
  input : StringView,
  base? : Url,
) -> Url raise ValidationErrors {
  let validation_errors = []
  match Url::try_parse(input, base?, validation_errors~) {
    Some(url) => url
    None => raise ValidationErrors(validation_errors)
  }
}

///|
/// Serialize URL to string per WHATWG URL spec.
/// See: https://url.spec.whatwg.org/#url-serializing
pub fn Url::to_string(self : Url) -> String {
  let result = StringBuilder::new()
  // 1. Let output be url's scheme and U+003A (:) concatenated.
  result.write_string(self.scheme)
  result.write_char(':')
  // 2. If url's host is non-null:
  match self.host {
    Some(host) => {
      // 2.1. Append "//" to output.
      result.write_string("//")
      // 2.2. If url includes credentials, then:
      if !self.username.is_empty() || !self.password.is_empty() {
        // 2.2.1. Append url's username to output.
        result.write_string(self.username)
        // 2.2.2. If url's password is not the empty string, then append U+003A (:),
        //        followed by url's password, to output.
        if !self.password.is_empty() {
          result.write_char(':')
          result.write_string(self.password)
        }
        // 2.2.3. Append U+0040 (@) to output.
        result.write_char('@')
      }
      // 2.3. Append url's host, serialized, to output.
      result.write_string(host.to_string())
      // 2.4. If url's port is non-null, append U+003A (:) followed by url's port,
      //      serialized, to output.
      match self.port {
        Some(port) => {
          result.write_char(':')
          result.write_string(port.to_string())
        }
        None => ()
      }
    }
    None =>
      // 3. If url's host is null, url does not have an opaque path, url's path's
      //    size is greater than 1, and url's path[0] is the empty string, then
      //    append U+002F (/) and U+002E (.) to output.
      //    (handled below in path serialization)
      // For file scheme without host, still include //
      if self.scheme == "file" {
        result.write_string("//")
      }
  }
  // 4. If url has an opaque path, then append url's path to output.
  // 5. Otherwise, then for each string in url's path, append U+002F (/) followed
  //    by the string to output.
  match self.path {
    Opaque(path) => result.write_string(path)
    Segments(segments) =>
      if segments.is_empty() && self.host is Some(_) && self.is_special() {
        result.write_char('/')
      } else {
        // 3. (continued) If url's host is null, url does not have an opaque path,
        //    url's path's size is greater than 1, and url's path[0] is the empty
        //    string, then append U+002F (/) and U+002E (.) to output.
        if self.host is None && segments.length() > 1 {
          match segments[0] {
            "" => result.write_string("/.")
            _ => ()
          }
        }
        for segment in segments {
          result.write_char('/')
          result.write_string(segment)
        }
      }
  }
  // 6. If url's query is non-null, append U+003F (?), followed by url's query,
  //    to output.
  match self.query {
    Some(query) => {
      result.write_char('?')
      result.write_string(query)
    }
    None => ()
  }
  // 7. If exclude fragment is false and url's fragment is non-null, then append
  //    U+0023 (#), followed by url's fragment, to output.
  match self.fragment {
    Some(fragment) => {
      result.write_char('#')
      result.write_string(fragment)
    }
    None => ()
  }
  // 8. Return output.
  result.to_string()
}

///|
/// Get the full URL as a string (alias for to_string)
pub fn Url::href(self : Url) -> String {
  self.to_string()
}

///|
/// Get the protocol (scheme + ":")
pub fn Url::protocol(self : Url) -> String {
  "\{self.scheme}:"
}

///|
/// Get the username
pub fn Url::username(self : Url) -> String {
  self.username
}

///|
/// Get the password
pub fn Url::password(self : Url) -> String {
  self.password
}

///|
/// Get the host (hostname + ":" + port if port is present)
pub fn Url::host(self : Url) -> String {
  match self.host {
    Some(host) => {
      let hostname = host.to_string()
      match self.port {
        Some(port) => "\{hostname}:\{port}"
        None => hostname
      }
    }
    None => ""
  }
}

///|
/// Get the hostname (serialized host without port)
pub fn Url::hostname(self : Url) -> String {
  match self.host {
    Some(host) => host.to_string()
    None => ""
  }
}

///|
/// Get the port as a string (empty string if no port or default port)
pub fn Url::port(self : Url) -> String {
  match self.port {
    Some(port) => port.to_string()
    None => ""
  }
}

///|
/// Get the pathname (serialized path)
pub fn Url::pathname(self : Url) -> String {
  self.path.to_string()
}

///|
/// Get the search (query string with leading "?" if present)
pub fn Url::search(self : Url) -> String {
  match self.query {
    Some(query) => if query.is_empty() { "" } else { "?\{query}" }
    None => ""
  }
}

///|
/// Get the URL search params.
/// Returns a new UrlSearchParams instance parsed from the current query string.
/// Note: Changes to the returned UrlSearchParams will NOT automatically update the URL.
/// Use set_search_params() to update the URL with modified search params.
/// See: https://url.spec.whatwg.org/#dom-url-searchparams
pub fn Url::search_params(self : Url) -> UrlSearchParams {
  match self.query {
    Some(query) => UrlSearchParams::from_string(query)
    None => UrlSearchParams::new()
  }
}

///|
/// Set the URL query from UrlSearchParams.
/// This serializes the search params and updates the URL's query.
pub fn Url::set_search_params(self : Url, params : UrlSearchParams) -> Unit {
  let serialized = params.to_string()
  if serialized.is_empty() {
    self.query = None
  } else {
    self.query = Some(serialized)
  }
}

///|
/// Get the hash (fragment with leading "#" if present)
pub fn Url::hash(self : Url) -> String {
  match self.fragment {
    Some(fragment) => if fragment.is_empty() { "" } else { "#\{fragment}" }
    None => ""
  }
}

///|
/// Get the origin (scheme + "://" + host + ":" + port for special schemes)
pub fn Url::origin(self : Url) -> String {
  if !is_special_scheme(self.scheme) {
    return "null"
  }
  if self.scheme == "file" {
    return "null"
  }
  match self.host {
    Some(host) => {
      let hostname = host.to_string()
      match self.port {
        Some(port) => "\{self.scheme}://\{hostname}:\{port}"
        None => "\{self.scheme}://\{hostname}"
      }
    }
    None => "null"
  }
}

///|
/// Get the default port for a scheme
fn default_port(scheme : StringView) -> UInt16? {
  match scheme {
    "ftp" => Some(21)
    "http" | "ws" => Some(80)
    "https" | "wss" => Some(443)
    _ => None
  }
}

///|
/// Run the basic URL parser with a state override on this URL.
fn Url::parse_with_state(
  self : Url,
  input : StringView,
  state_override : State,
) -> Unit {
  ignore(Url::parse_basic(self, input, state_override~))
}

///|
/// Set the full URL (re-parse and replace all components).
/// See: https://url.spec.whatwg.org/#dom-url-href
/// The href setter steps are:
/// 1. Let parsedURL be the result of running the basic URL parser on the given value.
/// 2. If parsedURL is failure, then throw a TypeError.
/// 3. Set this's URL to parsedURL.
pub fn Url::set_href(self : Url, href : String) -> Unit raise ValidationErrors {
  let parsed = parse(href)
  self.scheme = parsed.scheme
  self.username = parsed.username
  self.password = parsed.password
  self.host = parsed.host
  self.port = parsed.port
  self.path = parsed.path
  self.query = parsed.query
  self.fragment = parsed.fragment
}

///|
/// Set the protocol (scheme).
/// See: https://url.spec.whatwg.org/#dom-url-protocol
/// The protocol setter steps are to basic URL parse the given value, followed
/// by U+003A (:), with this's URL as url and scheme start state as state override.
pub fn Url::set_protocol(self : Url, protocol : String) -> Unit {
  let input = "\{protocol}:"
  self.parse_with_state(input[:], SchemeStart)
}

///|
/// Set the username.
/// See: https://url.spec.whatwg.org/#dom-url-username
/// The username setter steps are:
/// 1. If this's URL cannot have a username/password/port, then return.
/// 2. Set the username given this's URL and the given value.
pub fn Url::set_username(self : Url, username : String) -> Unit {
  // 1. If this's URL cannot have a username/password/port, then return.
  if self.host is None || self.host is Some(Opaque("")) {
    return
  }
  if self.scheme == "file" {
    return
  }
  // 2. Set the username given this's URL and the given value.
  // See: https://url.spec.whatwg.org/#set-the-username
  // Set url's username to the result of running UTF-8 percent-encode on the
  // given value using the userinfo percent-encode set.
  self.username = utf8_percent_encode(username, userinfo_percent_encode_set)
}

///|
/// Set the password.
/// See: https://url.spec.whatwg.org/#dom-url-password
/// The password setter steps are:
/// 1. If this's URL cannot have a username/password/port, then return.
/// 2. Set the password given this's URL and the given value.
pub fn Url::set_password(self : Url, password : String) -> Unit {
  // 1. If this's URL cannot have a username/password/port, then return.
  if self.host is None || self.host is Some(Opaque("")) {
    return
  }
  if self.scheme == "file" {
    return
  }
  // 2. Set the password given this's URL and the given value.
  // See: https://url.spec.whatwg.org/#set-the-password
  // Set url's password to the result of running UTF-8 percent-encode on the
  // given value using the userinfo percent-encode set.
  self.password = utf8_percent_encode(password, userinfo_percent_encode_set)
}

///|
/// Set the host (hostname and optional port).
/// See: https://url.spec.whatwg.org/#dom-url-host
/// The host setter steps are:
/// 1. If this's URL has an opaque path, then return.
/// 2. Basic URL parse the given value with this's URL as url and host state
///    as state override.
pub fn Url::set_host(self : Url, host_str : String) -> Unit {
  // 1. If this's URL has an opaque path, then return.
  if self.path is Opaque(_) {
    return
  }
  // 2. Basic URL parse the given value with this's URL as url and host state
  //    as state override.
  self.parse_with_state(host_str[:], Host)
}

///|
/// Set the hostname (without port).
/// See: https://url.spec.whatwg.org/#dom-url-hostname
/// The hostname setter steps are:
/// 1. If this's URL has an opaque path, then return.
/// 2. Basic URL parse the given value with this's URL as url and hostname state
///    as state override.
pub fn Url::set_hostname(self : Url, hostname : String) -> Unit {
  // 1. If this's URL has an opaque path, then return.
  if self.path is Opaque(_) {
    return
  }
  // 2. Basic URL parse the given value with this's URL as url and hostname state
  //    as state override.
  self.parse_with_state(hostname[:], Hostname)
}

///|
/// Set the port.
/// See: https://url.spec.whatwg.org/#dom-url-port
/// The port setter steps are:
/// 1. If this's URL cannot have a username/password/port, then return.
/// 2. If the given value is the empty string, then set this's URL's port to null.
/// 3. Otherwise, basic URL parse the given value with this's URL as url and
///    port state as state override.
pub fn Url::set_port(self : Url, port_str : String) -> Unit {
  // 1. If this's URL cannot have a username/password/port, then return.
  if has_empty_host(self.host) || self.path is Opaque(_) {
    return
  }
  if self.scheme == "file" {
    return
  }
  // 2. If the given value is the empty string, then set this's URL's port to null.
  if port_str.is_empty() {
    self.port = None
    return
  }
  // 3. Otherwise, basic URL parse the given value with this's URL as url and
  //    port state as state override.
  self.parse_with_state(port_str[:], Port)
}

///|
/// Set the pathname.
/// See: https://url.spec.whatwg.org/#dom-url-pathname
/// The pathname setter steps are:
/// 1. If this's URL has an opaque path, then return.
/// 2. Empty this's URL's path.
/// 3. Basic URL parse the given value with this's URL as url and path start
///    state as state override.
pub fn Url::set_pathname(self : Url, pathname : String) -> Unit {
  // 1. If this's URL has an opaque path, then return.
  if self.path is Opaque(_) {
    return
  }
  let cleaned = remove_ascii_tab_or_newline(pathname[:])
  if cleaned.is_empty() {
    if self.is_special() || self.host is None {
      self.path = Segments([""])
    } else {
      self.path = Segments([])
    }
    return
  }
  // 2. Empty this's URL's path.
  self.path = Segments([])
  // 3. Basic URL parse the given value with this's URL as url and path start
  //    state as state override.
  self.parse_with_state(cleaned[:], PathStart)
}

///|
/// Set the search (query string).
/// See: https://url.spec.whatwg.org/#dom-url-search
/// The search setter steps are:
/// 1. If the given value is the empty string, set this's URL's query to null.
/// 2. Otherwise:
///    2.1. Let input be the given value with a single leading U+003F (?) removed, if any.
///    2.2. Set this's URL's query to the empty string.
///    2.3. Basic URL parse input with this's URL as url and query state as
///         state override.
pub fn Url::set_search(self : Url, search : String) -> Unit {
  let cleaned = remove_ascii_tab_or_newline(search[:])
  // 1. If the given value is the empty string, set this's URL's query to null.
  if cleaned.is_empty() {
    self.query = None
    return
  }
  // 2.1. Let input be the given value with a single leading U+003F (?) removed, if any.
  let query = match cleaned[:] {
    ['?', .. rest] => rest
    _ => cleaned[:]
  }
  // 2.2. Set this's URL's query to the empty string.
  self.query = Some("")
  // 2.3. Basic URL parse input with this's URL as url and query state as state override.
  self.parse_with_state(query, Query)
}

///|
/// Set the hash (fragment).
/// See: https://url.spec.whatwg.org/#dom-url-hash
/// The hash setter steps are:
/// 1. If the given value is the empty string, then set this's URL's fragment to null.
/// 2. Otherwise:
///    2.1. Let input be the given value with a single leading U+0023 (#) removed, if any.
///    2.2. Set this's URL's fragment to the empty string.
///    2.3. Basic URL parse input with this's URL as url and fragment state as
///         state override.
pub fn Url::set_hash(self : Url, hash : String) -> Unit {
  let cleaned = remove_ascii_tab_or_newline(hash[:])
  // 1. If the given value is the empty string, then set this's URL's fragment to null.
  if cleaned.is_empty() {
    self.fragment = None
    return
  }
  // 2.1. Let input be the given value with a single leading U+0023 (#) removed, if any.
  let fragment = match cleaned[:] {
    ['#', .. rest] => rest
    _ => cleaned[:]
  }
  // 2.2. Set this's URL's fragment to the empty string.
  self.fragment = Some("")
  // 2.3. Basic URL parse input with this's URL as url and fragment state as
  //      state override.
  self.parse_with_state(fragment, Fragment)
}

///|
/// Implement Show trait for Url, outputting the serialized URL string
pub impl Show for Url with fn output(self : Url, logger : &Logger) -> Unit {
  logger.write_string(self.to_string())
}

///|
/// Serialize URL to JSON per WHATWG URL spec.
/// See: https://url.spec.whatwg.org/#dom-url-tojson
/// The toJSON() method returns the serialization of the URL.
pub impl ToJson for Url with fn to_json(self : Url) -> Json {
  self.to_string().to_json()
}

///|
/// Remove all ASCII tab (0x09), LF (0x0A), and CR (0x0D) from the entire input
fn remove_ascii_tab_or_newline(input : StringView) -> String {
  let output = StringBuilder::new()
  for c in input {
    if !(c is ('\t' | '\n' | '\r')) {
      output.write_char(c)
    }
  }
  output.to_string()
}

///|
fn has_empty_host(host : Host?) -> Bool {
  match host {
    None => true
    Some(Opaque("")) => true
    _ => false
  }
}

///|
/// Encode only the LAST trailing space as %20 for opaque paths before query/fragment
fn encode_last_trailing_space(s : String) -> String {
  // Check if the string ends with a space
  let len = s.length()
  if len == 0 {
    return s
  }
  match s.get_char(len - 1) {
    Some(' ') => {
      // Replace only the last space with %20
      let result = StringBuilder::new()
      for i, c in s {
        if i == len - 1 {
          result.write_string("%20")
        } else {
          result.write_char(c)
        }
      }
      result.to_string()
    }
    _ => s // No trailing space
  }
}

///|
/// Trim leading and trailing C0 control (U+0000-U+001F) or space (U+0020) from input
fn trim_c0_control_or_space(input : StringView) -> StringView {
  // Trim from start
  let input = for input = input {
    match input {
      ['\u{0000}'..='\u{0020}', .. rest] => continue rest
      input => break input
    }
  }
  // Trim from end
  let input = for input = input {
    match input {
      [.. rest, '\u{0000}'..='\u{0020}'] => continue rest
      input => break input
    }
  }
  input
}

///|
fn Url::is_special(url : Url) -> Bool {
  is_special_scheme(url.scheme)
}

///|
fn is_special_scheme(scheme : StringView) -> Bool {
  match scheme {
    "ftp" | "file" | "http" | "https" | "ws" | "wss" => true
    _ => false
  }
}

///|
/// Check if a path segment is a single-dot URL path segment per WHATWG spec
/// A single-dot URL path segment is "." or an ASCII case-insensitive match for "%2e"
fn is_single_dot_path_segment(segment : String) -> Bool {
  segment == "." || segment.to_lower() == "%2e"
}

///|
/// Check if a path segment is a double-dot URL path segment per WHATWG spec
/// A double-dot URL path segment is ".." or an ASCII case-insensitive match for ".%2e", "%2e.", or "%2e%2e"
fn is_double_dot_path_segment(segment : String) -> Bool {
  segment == ".." || segment.to_lower() is (".%2e" | "%2e." | "%2e%2e")
}

///|
/// Parser states per WHATWG URL spec.
/// See: https://url.spec.whatwg.org/#concept-basic-url-parser
priv enum State {
  /// https://url.spec.whatwg.org/#scheme-start-state
  SchemeStart
  /// https://url.spec.whatwg.org/#scheme-state
  Scheme
  /// https://url.spec.whatwg.org/#no-scheme-state
  NoScheme
  /// https://url.spec.whatwg.org/#file-state
  File
  /// https://url.spec.whatwg.org/#file-slash-state
  FileSlash
  /// https://url.spec.whatwg.org/#file-host-state
  FileHost
  /// https://url.spec.whatwg.org/#relative-state
  Relative
  /// https://url.spec.whatwg.org/#relative-slash-state
  RelativeSlash
  /// https://url.spec.whatwg.org/#special-relative-or-authority-state
  SpecialRelativeOrAuthority
  /// https://url.spec.whatwg.org/#special-authority-ignore-slashes-state
  SpecialAuthorityIgnoreSlashes
  /// https://url.spec.whatwg.org/#special-authority-slashes-state
  SpecialAuthoritySlashes
  /// https://url.spec.whatwg.org/#path-or-authority-state
  PathOrAuthority
  /// https://url.spec.whatwg.org/#authority-state
  Authority
  /// https://url.spec.whatwg.org/#host-state
  Host
  /// https://url.spec.whatwg.org/#hostname-state
  Hostname
  /// https://url.spec.whatwg.org/#port-state
  Port
  /// https://url.spec.whatwg.org/#path-start-state
  PathStart
  /// https://url.spec.whatwg.org/#path-state
  Path
  /// https://url.spec.whatwg.org/#opaque-path-state
  OpaquePath
  /// https://url.spec.whatwg.org/#query-state
  Query
  /// https://url.spec.whatwg.org/#fragment-state
  Fragment
}

///|
priv struct StringPointer {
  input : StringView
  mut pointer : Int
}

///|
fn StringPointer::new(input : StringView) -> StringPointer {
  { input, pointer: input.start_offset() }
}

///|
fn StringPointer::view(self : StringPointer) -> StringView {
  self.input.data().view(start_offset=self.pointer)
}

///|
fn StringPointer::next(self : StringPointer) -> StringView {
  match self.view() {
    [] as view => view
    [_, .. rest] => {
      self.pointer = rest.start_offset()
      rest
    }
  }
}

///|
fn StringPointer::increase(self : StringPointer) -> Unit {
  match self.view() {
    [] => ()
    [_, .. rest] => self.pointer = rest.start_offset()
  }
}

///|
fn StringPointer::decrease(self : StringPointer) -> Unit {
  // Get the StringView from start of input up to (but not including) current pointer
  let before = self.input.data().view(end_offset=self.pointer)
  match before {
    [] => () // Already at the start, do nothing
    [_] => self.pointer = before.start_offset() // One char, move to its start
    _ => {
      // Multiple chars, iterate to find the start of the last char
      let mut current_offset = before.start_offset()
      for view = before {
        match view {
          [_] => {
            self.pointer = current_offset
            break
          }
          [_, .. rest] => {
            current_offset = rest.start_offset()
            continue rest
          }
          [] => break // Shouldn't reach here
        }
      }
    }
  }
}

///|
fn StringPointer::start_over(self : StringPointer) -> Unit {
  self.pointer = self.input.start_offset()
}

///|
/// Basic URL parser per WHATWG URL spec.
/// See: https://url.spec.whatwg.org/#concept-basic-url-parser
fn Url::parse_basic(
  url : Url,
  base? : Url,
  input : StringView,
  state_override? : State,
  validation_errors? : Array[ValidationError] = [],
) -> Url? {
  let has_state_override = state_override is Some(_)
  // 2. If state override is not given, set state to scheme start state.
  let mut state : State = match state_override {
    Some(override_state) => override_state
    None => SchemeStart
  }
  // Special case: file URLs with host/hostname state override use FileHost
  if has_state_override &&
    url.scheme == "file" &&
    (state is Host || state is Hostname) {
    state = FileHost
  }
  // 3. Let buffer be the empty string.
  let buffer : StringBuilder = StringBuilder::new()
  // 4. Let atSignSeen, insideBrackets, and passwordTokenSeen be false.
  let mut at_sign_seen : Bool = false
  let mut inside_brackets : Bool = false
  let mut password_token_seen : Bool = false
  // 1. If input contains any ASCII tab or newline, validation error.
  //    (Note: We also remove leading/trailing C0 control or space per step 1)
  // Per WHATWG: 1. Remove leading/trailing C0 control or space
  //             2. Remove all ASCII tab or newline from entire input
  let input = if has_state_override {
    input
  } else {
    trim_c0_control_or_space(input)
  }
  let input = remove_ascii_tab_or_newline(input)
  // Handle empty input per WHATWG spec (only for full parse)
  if input.is_empty() && !has_state_override {
    match base {
      Some(base) => {
        url.scheme = base.scheme
        url.username = base.username
        url.password = base.password
        url.host = base.host
        url.port = base.port
        url.path = base.path.clone()
        url.query = base.query
        // fragment is NOT copied per WHATWG spec
        return Some(url)
      }
      None => {
        validation_errors.push(MissingSchemeNonRelativeUrl)
        return None
      }
    }
  }
  let pointer = StringPointer::new(input[:])
  // Outer loop for restart mechanism (WHATWG "start over")
  while true {
    for view = pointer.view(); ; view = pointer.next() {
      guard view is [c, .. remaining] else { break }
      match state {
        // Scheme start state (https://url.spec.whatwg.org/#scheme-start-state)
        // 1. If c is an ASCII alpha, append c, lowercased, to buffer, and set
        //    state to scheme state.
        // 2. Otherwise, if state override is not given, set state to no scheme
        //    state and decrease pointer by 1.
        // 3. Otherwise, return failure.
        SchemeStart =>
          if c.is_ascii_alphabetic() {
            buffer.write_char(c.to_ascii_lowercase())
            state = Scheme
          } else if !has_state_override {
            state = NoScheme
            continue view // Re-process same character in NoScheme
          } else {
            return None
          }
        // Scheme state (https://url.spec.whatwg.org/#scheme-state)
        // 1. If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.),
        //    append c, lowercased, to buffer.
        // 2. Otherwise, if c is U+003A (:), then: [complex logic follows]
        // 3. Otherwise, if state override is not given, set buffer to empty,
        //    state to no scheme state, and start over.
        // 4. Otherwise, return failure.
        Scheme =>
          match c {
            'a'..='z' | 'A'..='Z' | '0'..='9' | '+' | '-' | '.' =>
              buffer.write_char(c.to_ascii_lowercase())
            ':' => {
              let next_scheme = buffer.to_string()
              if has_state_override {
                let url_special = url.is_special()
                let next_special = is_special_scheme(next_scheme)
                if url_special && !next_special {
                  return Some(url)
                }
                if !url_special && next_special {
                  return Some(url)
                }
                if (
                    !url.username.is_empty() ||
                    !url.password.is_empty() ||
                    url.port is Some(_)
                  ) &&
                  next_scheme == "file" {
                  return Some(url)
                }
                if url.scheme == "file" && has_empty_host(url.host) {
                  return Some(url)
                }
              }
              url.scheme = next_scheme
              buffer.reset()
              if has_state_override {
                match url.port {
                  Some(port) =>
                    if default_port(url.scheme) is Some(default) &&
                      port == default {
                      url.port = None
                    }
                  None => ()
                }
                return Some(url)
              }
              if url.scheme is "file" {
                if remaining.has_prefix("//") {
                  validation_errors.push(SpecialSchemeMissingFollowingSolidus)
                }
                state = File
              } else if is_special_scheme(url.scheme) &&
                base is Some(base) &&
                base.scheme == url.scheme {
                guard is_special_scheme(base.scheme) else {
                  abort("is_special_scheme(base.scheme) must be true")
                }
                state = SpecialRelativeOrAuthority
              } else if is_special_scheme(url.scheme) {
                state = SpecialAuthoritySlashes
              } else if remaining is ['/', ..] {
                state = PathOrAuthority
                pointer.increase()
              } else {
                url.path = Opaque("")
                state = OpaquePath
              }
            }
            _ =>
              if has_state_override {
                return None
              } else {
                buffer.reset()
                state = NoScheme
                pointer.start_over()
                continue pointer.view()
              }
          }
        // No scheme state (https://url.spec.whatwg.org/#no-scheme-state)
        // 1. If base is null, or base has an opaque path and c is not U+0023 (#),
        //    missing-scheme-non-relative-URL validation error, return failure.
        // 2. Otherwise, if base has an opaque path and c is U+0023 (#), set url's
        //    scheme/path/query from base, set url's fragment to empty, set state
        //    to fragment state.
        // 3. Otherwise, if base's scheme is not "file", set state to relative state
        //    and decrease pointer by 1.
        // 4. Otherwise, set state to file state and decrease pointer by 1.
        NoScheme =>
          match base {
            None => {
              // 1. Base is null
              validation_errors.push(MissingSchemeNonRelativeUrl)
              return None
            }
            Some({ path: Opaque(_), .. }) if !(c is '#') => {
              // 1. Base has opaque path and c is not #
              validation_errors.push(MissingSchemeNonRelativeUrl)
              return None
            }
            Some({ path: Opaque(_), .. } as base) if c is '#' => {
              // 2. Base has opaque path and c is #
              url.scheme = base.scheme
              url.path = base.path.clone()
              url.query = base.query
              url.fragment = Some("")
              state = Fragment
            }
            Some({ scheme, .. }) if scheme != "file" => {
              // 3. Base's scheme is not "file"
              state = Relative
              continue view // Re-process same character in Relative
            }
            _ => {
              // 4. Base's scheme is "file"
              state = File
              continue view // Re-process same character in File
            }
          }
        // Special relative or authority state
        // (https://url.spec.whatwg.org/#special-relative-or-authority-state)
        // 1. If c is U+002F (/) and remaining starts with U+002F (/), then set
        //    state to special authority ignore slashes state and increase pointer by 1.
        // 2. Otherwise, special-scheme-missing-following-solidus validation error,
        //    set state to relative state and decrease pointer by 1.
        SpecialRelativeOrAuthority =>
          if c is '/' && remaining.has_prefix("/") {
            state = SpecialAuthorityIgnoreSlashes
            pointer.increase()
          } else {
            validation_errors.push(SpecialSchemeMissingFollowingSolidus)
            state = Relative
            continue view // Re-process same character in Relative
          }
        // Path or authority state (https://url.spec.whatwg.org/#path-or-authority-state)
        // 1. If c is U+002F (/), then set state to authority state.
        // 2. Otherwise, set state to path state, and decrease pointer by 1.
        PathOrAuthority =>
          if c is '/' {
            state = Authority
          } else {
            state = Path
            continue view // Re-process same character in Path
          }
        // Relative state (https://url.spec.whatwg.org/#relative-state)
        Relative => {
          guard base is Some(base) else {
            abort("base must be Some(base) in Relative state")
          }
          guard base.scheme != "file" else {
            abort("base.scheme must not be \"file\" in Relative state")
          }
          url.scheme = base.scheme
          if c is '/' {
            state = RelativeSlash
          } else if url.is_special() && c is '\\' {
            validation_errors.push(InvalidReverseSolidus)
            state = RelativeSlash
          } else {
            url.username = base.username
            url.password = base.password
            url.host = base.host
            url.port = base.port
            url.path = base.path.clone()
            url.query = base.query
            if c is '?' {
              url.query = Some("")
              state = Query
            } else if c is '#' {
              url.fragment = Some("")
              state = Fragment
            } else {
              url.query = None
              url.path.shorten(scheme=url.scheme)
              state = Path
              continue view // Re-process same character in Path
            }
          }
        }
        // Relative slash state (https://url.spec.whatwg.org/#relative-slash-state)
        RelativeSlash => {
          guard base is Some(base) else {
            abort("base must be Some(base) in RelativeSlash state")
          }
          // 1. If url is special and c is U+002F (/) or U+005C (\), then:
          if url.is_special() && c is ('/' | '\\') {
            // 1.1. If c is U+005C (\), invalid-reverse-solidus validation error.
            if c is '/' {
              validation_errors.push(InvalidReverseSolidus)
            }
            // 1.2. Set state to special authority ignore slashes state.
            state = SpecialAuthorityIgnoreSlashes
          } else if c is '/' {
            // 2. Otherwise, if c is U+002F (/), then set state to authority state.
            state = Authority
          } else {
            // 3. Otherwise, set url's username/password/host/port from base,
            //    state to path state, and decrease pointer by 1.
            url.username = base.username
            url.password = base.password
            url.host = base.host
            url.port = base.port
            state = Path
            continue view // Re-process same character in Path
          }
        }
        // Special authority slashes state
        // (https://url.spec.whatwg.org/#special-authority-slashes-state)
        // 1. If c is U+002F (/) and remaining starts with U+002F (/), then set
        //    state to special authority ignore slashes state and increase pointer by 1.
        // 2. Otherwise, special-scheme-missing-following-solidus validation error,
        //    set state to special authority ignore slashes state and decrease pointer by 1.
        SpecialAuthoritySlashes =>
          if c is '/' && remaining.has_prefix("/") {
            state = SpecialAuthorityIgnoreSlashes
            pointer.increase()
          } else {
            validation_errors.push(SpecialSchemeMissingFollowingSolidus)
            state = SpecialAuthorityIgnoreSlashes
            continue view // Re-process same character
          }
        // Special authority ignore slashes state
        // (https://url.spec.whatwg.org/#special-authority-ignore-slashes-state)
        // 1. If c is neither U+002F (/) nor U+005C (\), then set state to authority
        //    state and decrease pointer by 1.
        // 2. Otherwise, special-scheme-missing-following-solidus validation error.
        SpecialAuthorityIgnoreSlashes =>
          if !(c is ('/' | '\\')) {
            state = Authority
            continue view // Re-process same character in Authority
          } else {
            validation_errors.push(SpecialSchemeMissingFollowingSolidus)
          }
        // Authority state (https://url.spec.whatwg.org/#authority-state)
        Authority =>
          if c is '@' {
            validation_errors.push(InvalidCredentials)
            if at_sign_seen {
              let prepend = "%40\{buffer.to_string()}"
              buffer.reset()
              buffer.write_string(prepend)
            }
            at_sign_seen = true
            for code_point in buffer.to_string() {
              if code_point is ':' && !password_token_seen {
                password_token_seen = true
                continue
              }
              let encoded_code_points = utf8_percent_encode(
                [code_point],
                userinfo_percent_encode_set,
              )
              if password_token_seen {
                url.password = "\{url.password}\{encoded_code_points}"
              } else {
                url.username = "\{url.username}\{encoded_code_points}"
              }
            }
            buffer.reset()
          } else if c is ('/' | '?' | '#') || (url.is_special() && c is '\\') {
            if at_sign_seen && buffer.is_empty() {
              validation_errors.push(HostMissing)
              return None
            }
            for _ in buffer.to_string() {
              pointer.decrease()
            }
            pointer.decrease()
            buffer.reset()
            state = Host
          } else {
            buffer.write_char(c)
          }
        // Host state (https://url.spec.whatwg.org/#host-state)
        // Hostname state (https://url.spec.whatwg.org/#hostname-state)
        // These states are nearly identical, with hostname state not transitioning to port.
        Host | Hostname => {
          if has_state_override && url.scheme == "file" {
            state = FileHost
            continue view
          }
          // 1. If state override is given and url's scheme is "file", then decrease
          //    pointer by 1 and set state to file host state.
          // 2. Otherwise, if c is U+003A (:) and insideBrackets is false, then:
          // 3. Otherwise, if c is the EOF code point, U+002F (/), U+003F (?), or
          //    U+0023 (#), or url is special and c is U+005C (\), then decrease
          //    pointer by 1 and: [process host]
          // 4. Otherwise, append c to buffer.
          if c is '[' {
            inside_brackets = true
            buffer.write_char(c)
          } else if c is ']' {
            inside_brackets = false
            buffer.write_char(c)
          } else if c is ':' && !inside_brackets {
            if buffer.is_empty() {
              validation_errors.push(HostMissing)
              return None
            }
            if state_override is Some(Hostname) {
              return None
            }
            let host = Host::parse(
              buffer.to_string(),
              is_opaque=!url.is_special(),
            ) catch {
              err => {
                validation_errors.push(err)
                return None
              }
            }
            url.host = Some(host)
            buffer.reset()
            state = Port
          } else if c is ('/' | '?' | '#') || (url.is_special() && c is '\\') {
            pointer.decrease()
            if url.is_special() && buffer.is_empty() {
              validation_errors.push(HostMissing)
              return None
            }
            if has_state_override &&
              buffer.is_empty() &&
              (
                !url.username.is_empty() ||
                !url.password.is_empty() ||
                url.port is Some(_)
              ) {
              return None
            }
            if buffer.is_empty() {
              url.host = Some(Opaque(""))
            } else {
              let host = Host::parse(
                buffer.to_string(),
                is_opaque=!url.is_special(),
              ) catch {
                err => {
                  validation_errors.push(err)
                  return None
                }
              }
              url.host = Some(host)
            }
            buffer.reset()
            state = PathStart
            if has_state_override {
              return Some(url)
            }
          } else {
            buffer.write_char(c)
          }
        }
        // Port state (https://url.spec.whatwg.org/#port-state)
        // 1. If c is an ASCII digit, append c to buffer.
        // 2. Otherwise, if c is the EOF code point, U+002F (/), U+003F (?), or
        //    U+0023 (#), or url is special and c is U+005C (\), or state override
        //    is given, then: [process port]
        // 3. Otherwise, return failure.
        Port =>
          if c.is_ascii_digit() {
            buffer.write_char(c)
          } else if c is ('/' | '?' | '#') ||
            (url.is_special() && c is '\\') ||
            has_state_override {
            if !buffer.is_empty() {
              let port_str = buffer.to_string()
              let mut port_value : UInt64 = 0
              for ch in port_str {
                port_value = port_value * 10 + (ch.to_int() - '0').to_uint64()
                // Check overflow during accumulation to prevent UInt64 wraparound
                if port_value > 65535 {
                  return None
                }
              }
              // Check if port is default for the scheme
              let port_u16 = port_value.to_uint16()
              let is_default = match url.scheme {
                "ftp" => port_u16 == 21
                "http" | "ws" => port_u16 == 80
                "https" | "wss" => port_u16 == 443
                _ => false
              }
              url.port = if is_default { None } else { Some(port_u16) }
              buffer.reset()
              if has_state_override {
                return Some(url)
              }
            } else if has_state_override {
              return None
            }
            state = PathStart
            pointer.decrease()
          } else {
            // Invalid port character
            return None
          }
        // Path start state (https://url.spec.whatwg.org/#path-start-state)
        // 1. If url is special, then:
        //    1.1. If c is U+005C (\), invalid-reverse-solidus validation error.
        //    1.2. Set state to path state.
        //    1.3. If c is neither U+002F (/) nor U+005C (\), then decrease pointer by 1.
        // 2. Otherwise, if state override is not given and c is U+003F (?), set
        //    url's query to the empty string and state to query state.
        // 3. Otherwise, if state override is not given and c is U+0023 (#), set
        //    url's fragment to the empty string and state to fragment state.
        // 4. Otherwise, if c is not the EOF code point:
        //    4.1. Set state to path state.
        //    4.2. If c is not U+002F (/), then decrease pointer by 1.
        PathStart =>
          if url.is_special() {
            if c is '\\' {
              validation_errors.push(InvalidReverseSolidus)
            }
            state = Path
            if !(c is ('/' | '\\')) {
              continue view
            }
          } else if !has_state_override && c is '?' {
            url.query = Some("")
            state = Query
          } else if !has_state_override && c is '#' {
            url.fragment = Some("")
            state = Fragment
          } else {
            state = Path
            if !(c is '/') {
              continue view
            }
          }
        // Path state (https://url.spec.whatwg.org/#path-state)
        Path =>
          if c is '/' ||
            (url.is_special() && c is '\\') ||
            (!has_state_override && c is ('?' | '#')) {
            if url.is_special() && c is '\\' {
              validation_errors.push(InvalidReverseSolidus)
            }
            let buffer_str = buffer.to_string()
            // Handle . and .. path segments
            if is_double_dot_path_segment(buffer_str) {
              url.path.shorten(scheme=url.scheme)
              if !(c is ('/' | '\\')) {
                match url.path {
                  Segments(segments) => segments.push("")
                  _ => ()
                }
              }
            } else if is_single_dot_path_segment(buffer_str) {
              if !(c is ('/' | '\\')) {
                match url.path {
                  Segments(segments) => segments.push("")
                  _ => ()
                }
              }
            } else {
              if url.scheme == "file" &&
                url.path is Segments(segments) &&
                segments.is_empty() {
                // Windows drive letter check
                if buffer_str.length() == 2 {
                  match buffer_str {
                    [letter, ':' | '|'] if letter.is_ascii_alphabetic() => {
                      // Normalize Windows drive letter (preserve case, replace | with :)
                      let normalized = "\{letter}:"
                      match url.path {
                        Segments(segments) => segments.push(normalized)
                        _ => ()
                      }
                      buffer.reset()
                      if c is '?' {
                        url.query = Some("")
                        state = Query
                        continue pointer.next()
                      } else if c is '#' {
                        url.fragment = Some("")
                        state = Fragment
                        continue pointer.next()
                      }
                      continue pointer.next()
                    }
                    _ => ()
                  }
                }
              }
              match url.path {
                Segments(segments) =>
                  segments.push(
                    utf8_percent_encode(buffer_str, path_percent_encode_set),
                  )
                Opaque(s) =>
                  url.path = Opaque(
                    s +
                    "/" +
                    utf8_percent_encode(buffer_str, path_percent_encode_set),
                  )
              }
            }
            buffer.reset()
            if c is '?' {
              url.query = Some("")
              state = Query
            } else if c is '#' {
              url.fragment = Some("")
              state = Fragment
            }
            // Percent-encode if needed
          } else if c is '%' {
            // Per WHATWG: % followed by non-hex is a validation error but pass through literally
            // (% is not in the path percent-encode set so it shouldn't be encoded)
            buffer.write_char(c)
          } else if path_percent_encode_set(c) {
            // This character should be percent-encoded
            let bytes = @encoding/utf8.encode([c])
            for byte in bytes {
              buffer.write_char('%')
              let b = byte.to_int()
              buffer.write_char(hex_digits[(b >> 4) & 0x0f])
              buffer.write_char(hex_digits[b & 0x0f])
            }
          } else {
            buffer.write_char(c)
          }
        // Opaque path state (https://url.spec.whatwg.org/#opaque-path-state)
        // 1. If c is U+003F (?), then set url's query to the empty string and
        //    state to query state.
        // 2. Otherwise, if c is U+0023 (#), then set url's fragment to the empty
        //    string and state to fragment state.
        // 3. Otherwise: [if not EOF, UTF-8 percent-encode c using C0 control
        //    percent-encode set and append result to url's path]
        OpaquePath =>
          if c is '?' {
            // Encode trailing spaces in opaque path before transitioning to Query
            match url.path {
              Opaque(s) => url.path = Opaque(encode_last_trailing_space(s))
              _ => ()
            }
            url.query = Some("")
            state = Query
          } else if c is '#' {
            // Encode trailing spaces in opaque path before transitioning to Fragment
            match url.path {
              Opaque(s) => url.path = Opaque(encode_last_trailing_space(s))
              _ => ()
            }
            url.fragment = Some("")
            state = Fragment
          } else {
            match url.path {
              Opaque(s) =>
                if c0_control_percent_encode_set(c) {
                  let bytes = @encoding/utf8.encode([c])
                  let mut encoded = s
                  for byte in bytes {
                    let b = byte.to_int()
                    encoded = "\{encoded}%\{hex_digits[(b >> 4) & 0x0f]}\{hex_digits[b & 0x0f]}"
                  }
                  url.path = Opaque(encoded)
                } else {
                  url.path = Opaque("\{s}\{c}")
                }
              _ => ()
            }
          }
        // Query state (https://url.spec.whatwg.org/#query-state)
        // 1. If state override is not given and c is U+0023 (#), then set url's
        //    fragment to the empty string and state to fragment state.
        // 2. Otherwise, if c is not the EOF code point:
        //    2.1. If url is special and url's scheme is not "ws" or "wss", then
        //         use the special-query percent-encode set.
        //    2.2. UTF-8 percent-encode c using the appropriate encode set, and
        //         append the result to url's query.
        Query =>
          if !has_state_override && c is '#' {
            url.fragment = Some("")
            state = Fragment
          } else {
            // Percent-encode query characters
            // Use special query encode set for special URLs (encodes ')
            let encode_set = if url.is_special() {
              special_query_percent_encode_set
            } else {
              query_percent_encode_set
            }
            match url.query {
              Some(q) =>
                if c is '%' {
                  // Per WHATWG: % followed by non-hex is a validation error but pass through literally
                  // (% is not in the query percent-encode set so it shouldn't be encoded)
                  url.query = Some("\{q}\{c}")
                } else if encode_set(c) {
                  let bytes = @encoding/utf8.encode([c])
                  let mut encoded = q
                  for byte in bytes {
                    let b = byte.to_int()
                    encoded = "\{encoded}%\{hex_digits[(b >> 4) & 0x0f]}\{hex_digits[b & 0x0f]}"
                  }
                  url.query = Some(encoded)
                } else {
                  url.query = Some("\{q}\{c}")
                }
              None => ()
            }
          }
        // Fragment state (https://url.spec.whatwg.org/#fragment-state)
        // If c is not the EOF code point, then:
        //   UTF-8 percent-encode c using the fragment percent-encode set and
        //   append the result to url's fragment.
        Fragment =>
          match url.fragment {
            Some(f) =>
              if c is '%' {
                // Per WHATWG: % followed by non-hex is a validation error but pass through literally
                // (% is not in the fragment percent-encode set so it shouldn't be encoded)
                url.fragment = Some("\{f}\{c}")
              } else if fragment_percent_encode_set(c) {
                let bytes = @encoding/utf8.encode([c])
                let mut encoded = f
                for byte in bytes {
                  let b = byte.to_int()
                  encoded = "\{encoded}%\{hex_digits[(b >> 4) & 0x0f]}\{hex_digits[b & 0x0f]}"
                }
                url.fragment = Some(encoded)
              } else {
                url.fragment = Some("\{f}\{c}")
              }
            None => ()
          }
        // File state (https://url.spec.whatwg.org/#file-state)
        // 1. Set url's scheme to "file".
        // 2. Set url's host to the empty string.
        // 3. If c is U+002F (/) or U+005C (\), then: [set state to file slash state]
        // 4. Otherwise, if base is non-null and base's scheme is "file": [copy from base]
        // 5. Otherwise: [set state to path state]
        File => {
          url.scheme = "file"
          if c is ('/' | '\\') {
            if c is '\\' {
              validation_errors.push(InvalidReverseSolidus)
            }
            state = FileSlash
          } else if base is Some(base) && base.scheme == "file" {
            url.host = base.host
            url.path = base.path.clone()
            url.query = base.query
            if c is '?' {
              url.query = Some("")
              state = Query
            } else if c is '#' {
              url.fragment = Some("")
              state = Fragment
            } else {
              url.query = None
              // Check for Windows drive letter - c is the potential letter
              let starts_with_drive = if c.is_ascii_alphabetic() {
                match remaining {
                  [':' | '|', .. rest] =>
                    rest is ([] | ['/' | '\\' | '?' | '#', ..])
                  _ => false
                }
              } else {
                false
              }
              if starts_with_drive {
                // Reset path for drive letter, don't shorten base path
                url.path = Segments([])
              } else {
                url.path.shorten(scheme="file")
              }
              state = Path
              continue view // Re-process same character in Path
            }
          } else {
            // No base or base is not file scheme - set empty host for proper file:/// serialization
            url.host = Some(Opaque(""))
            state = Path
            continue view // Re-process same character in Path
          }
        }
        // File slash state (https://url.spec.whatwg.org/#file-slash-state)
        // 1. If c is U+002F (/) or U+005C (\), then:
        //    1.1. If c is U+005C (\), invalid-reverse-solidus validation error.
        //    1.2. Set state to file host state.
        // 2. Otherwise: [check for Windows drive letter, set host/path from base if available]
        FileSlash => {
          url.scheme = "file"
          if c is ('/' | '\\') {
            if c is '\\' {
              validation_errors.push(InvalidReverseSolidus)
            }
            state = FileHost
          } else {
            // Check if current input starts with a Windows drive letter
            // e.g., for "/c:/foo", c='c', remaining=":/foo"
            let input_has_drive = c.is_ascii_alphabetic() &&
              remaining is [':' | '|', ..]
            if base is Some(base) && base.scheme == "file" {
              url.host = base.host
              // Only copy base drive letter if input doesn't have its own
              if !input_has_drive {
                match base.path {
                  Segments([drive, ..]) if drive.length() == 2 =>
                    match drive {
                      [letter, ':'] if letter.is_ascii_alphabetic() =>
                        url.path = Segments([drive])
                      _ => ()
                    }
                  _ => ()
                }
              }
            } else {
              // No base - set empty host for proper file:/// serialization
              url.host = Some(Opaque(""))
            }
            state = Path
            continue view // Re-process same character in Path
          }
        }
        // File host state (https://url.spec.whatwg.org/#file-host-state)
        // 1. If c is the EOF code point, U+002F (/), U+005C (\), U+003F (?), or
        //    U+0023 (#), then decrease pointer by 1 and then:
        //    1.1. If state override is not given and buffer is a Windows drive letter,
        //         set state to path state. (Note: This is a file: URL quirk.)
        //    1.2. Otherwise, if buffer is the empty string, then set url's host
        //         to the empty string and set state to path start state.
        //    1.3. Otherwise, run these steps: [parse host, handle "localhost"]
        // 2. Otherwise, append c to buffer.
        FileHost => {
          url.scheme = "file"
          if c is ('/' | '\\' | '?' | '#') {
            pointer.decrease()
            // Check for Windows drive letter
            let buffer_str = buffer.to_string()
            if buffer_str.length() == 2 {
              match buffer_str {
                [letter, ':' | '|'] if letter.is_ascii_alphabetic() =>
                  // It's a Windows drive letter, not a host
                  state = Path
                _ =>
                  if buffer_str.is_empty() {
                    url.host = Some(Opaque(""))
                    state = PathStart
                  } else {
                    let host = Host::parse(buffer_str) catch {
                      err => {
                        validation_errors.push(err)
                        return None
                      }
                    }
                    if host is Domain("localhost") {
                      url.host = Some(Opaque(""))
                    } else {
                      url.host = Some(host)
                    }
                    buffer.reset()
                    state = PathStart
                  }
              }
            } else if buffer_str.is_empty() {
              url.host = Some(Opaque(""))
              state = PathStart
            } else {
              let host = Host::parse(buffer_str) catch {
                err => {
                  validation_errors.push(err)
                  return None
                }
              }
              if host is Domain("localhost") {
                url.host = Some(Opaque(""))
              } else {
                url.host = Some(host)
              }
              buffer.reset()
              state = PathStart
            }
          } else {
            buffer.write_char(c)
          }
        }
      }
    }
    // Handle EOF based on current state
    match state {
      SchemeStart | NoScheme => {
        if has_state_override {
          return None
        }
        validation_errors.push(MissingSchemeNonRelativeUrl)
        return None
      }
      Scheme =>
        // EOF in Scheme state without finding ':' - try as NoScheme if base exists
        if has_state_override {
          return None
        } else if base is Some(_) {
          // Reset and re-parse in NoScheme state
          buffer.reset()
          state = NoScheme
          pointer.start_over()
          // Restart the outer loop to re-parse from NoScheme
          continue
        } else {
          validation_errors.push(MissingSchemeNonRelativeUrl)
          return None
        }
      Authority => {
        // At EOF in Authority state - buffer contains host[:port] (no @ was seen)
        // or remaining host[:port] after @
        if at_sign_seen && buffer.is_empty() {
          validation_errors.push(HostMissing)
          return None
        }
        let buffer_str = buffer.to_string()
        if buffer_str.is_empty() {
          if url.is_special() {
            validation_errors.push(HostMissing)
            return None
          }
          url.host = Some(Opaque(""))
        } else {
          // Check for host:port pattern
          // For IPv6 addresses like [2001::1]:80, the port separator is after the closing ]
          let mut port_separator = -1
          if buffer_str.has_prefix("[") {
            // IPv6 address - look for : after ]
            let mut found_bracket = false
            for i, c in buffer_str {
              if c == ']' {
                found_bracket = true
              } else if found_bracket && c == ':' {
                port_separator = i
                break
              }
            }
          } else {
            // Regular host - last colon is port separator
            for i, c in buffer_str {
              if c == ':' {
                port_separator = i
              }
            }
          }
          let has_port = port_separator > 0
          if has_port {
            // Split at port separator
            let last_colon = port_separator
            if last_colon > 0 {
              // Extract host and port using character iteration
              let host_builder = StringBuilder::new()
              let port_builder = StringBuilder::new()
              let mut past_colon = false
              let mut char_idx = 0
              for c in buffer_str {
                if char_idx == last_colon {
                  past_colon = true
                } else if past_colon {
                  port_builder.write_char(c)
                } else {
                  host_builder.write_char(c)
                }
                char_idx = char_idx + 1
              }
              let host_part = host_builder.to_string()
              let port_part = port_builder.to_string()
              // Parse host
              let host = Host::parse(host_part, is_opaque=!url.is_special()) catch {
                err => {
                  validation_errors.push(err)
                  return None
                }
              }
              url.host = Some(host)
              // Parse port
              if !port_part.is_empty() {
                let mut port_value : UInt64 = 0
                for ch in port_part {
                  if !ch.is_ascii_digit() {
                    return None // Invalid port character
                  }
                  port_value = port_value * 10 + (ch.to_int() - '0').to_uint64()
                  if port_value > 65535 {
                    return None
                  }
                }
                let port_u16 = port_value.to_uint16()
                let is_default = match url.scheme {
                  "ftp" => port_u16 == 21
                  "http" | "ws" => port_u16 == 80
                  "https" | "wss" => port_u16 == 443
                  _ => false
                }
                url.port = if is_default { None } else { Some(port_u16) }
              }
            } else {
              // Colon at start means empty host - parse whole buffer as host
              let host = Host::parse(buffer_str, is_opaque=!url.is_special()) catch {
                err => {
                  validation_errors.push(err)
                  return None
                }
              }
              url.host = Some(host)
            }
          } else {
            // No port, just parse as host
            let host = Host::parse(buffer_str, is_opaque=!url.is_special()) catch {
              err => {
                validation_errors.push(err)
                return None
              }
            }
            url.host = Some(host)
          }
        }
      }
      Host | Hostname => {
        if url.is_special() && buffer.is_empty() {
          validation_errors.push(HostMissing)
          return None
        }
        if has_state_override &&
          buffer.is_empty() &&
          (
            !url.username.is_empty() ||
            !url.password.is_empty() ||
            url.port is Some(_)
          ) {
          return None
        }
        if !buffer.is_empty() {
          let host = Host::parse(
            buffer.to_string(),
            is_opaque=!url.is_special(),
          ) catch {
            err => {
              validation_errors.push(err)
              return None
            }
          }
          url.host = Some(host)
        } else if !url.is_special() {
          url.host = Some(Opaque(""))
        }
      }
      Port =>
        if !buffer.is_empty() {
          let port_str = buffer.to_string()
          let mut port_value : UInt64 = 0
          for ch in port_str {
            port_value = port_value * 10 + (ch.to_int() - '0').to_uint64()
            // Check overflow during accumulation to prevent UInt64 wraparound
            if port_value > 65535 {
              return None
            }
          }
          let port_u16 = port_value.to_uint16()
          let is_default = match url.scheme {
            "ftp" => port_u16 == 21
            "http" | "ws" => port_u16 == 80
            "https" | "wss" => port_u16 == 443
            _ => false
          }
          url.port = if is_default { None } else { Some(port_u16) }
        } else if has_state_override {
          return None
        }
      PathStart =>
        if has_state_override && url.host is None {
          match url.path {
            Segments(segments) => segments.push("")
            _ => ()
          }
        }
      Path => {
        let buffer_str = buffer.to_string()
        if is_double_dot_path_segment(buffer_str) {
          url.path.shorten(scheme=url.scheme)
          // Push empty segment for trailing slash
          match url.path {
            Segments(segments) => segments.push("")
            _ => ()
          }
        } else if is_single_dot_path_segment(buffer_str) {
          // Single dot at EOF - push empty segment for trailing slash
          match url.path {
            Segments(segments) => segments.push("")
            _ => ()
          }
          // Check for Windows drive letter normalization at EOF (e.g., "C|" -> "C:")
        } else if url.scheme == "file" &&
          url.path is Segments(segments) &&
          segments.is_empty() &&
          buffer_str.length() == 2 {
          match buffer_str {
            [letter, ':' | '|'] if letter.is_ascii_alphabetic() =>
              match url.path {
                Segments(segments) => segments.push("\{letter}:")
                _ => ()
              }
            _ =>
              match url.path {
                Segments(segments) =>
                  segments.push(
                    utf8_percent_encode(buffer_str, path_percent_encode_set),
                  )
                Opaque(s) =>
                  url.path = Opaque(
                    s +
                    "/" +
                    utf8_percent_encode(buffer_str, path_percent_encode_set),
                  )
              }
          }
        } else {
          // Push segment (may be empty for trailing slash in non-special URLs)
          match url.path {
            Segments(segments) =>
              segments.push(
                utf8_percent_encode(buffer_str, path_percent_encode_set),
              )
            Opaque(s) =>
              url.path = Opaque(
                s +
                "/" +
                utf8_percent_encode(buffer_str, path_percent_encode_set),
              )
          }
        }
      }
      FileHost => {
        url.scheme = "file"
        let buffer_str = buffer.to_string()
        if buffer_str.is_empty() {
          url.host = Some(Opaque(""))
        } else {
          // Check for Windows drive letter using explicit char access
          let is_drive_letter = buffer_str.length() == 2 &&
            (match buffer_str.get_char(0) {
              Some(c) => c.is_ascii_alphabetic()
              None => false
            }) &&
            (match buffer_str.get_char(1) {
              Some(':') | Some('|') => true
              _ => false
            })
          if is_drive_letter {
            // Windows drive letter in host position (normalize | to :)
            url.host = Some(Opaque(""))
            let letter = buffer_str.get_char(0).unwrap()
            url.path = Segments(["\{letter}:"])
          } else {
            let host = Host::parse(buffer_str) catch {
              err => {
                validation_errors.push(err)
                return None
              }
            }
            if host is Domain("localhost") {
              url.host = Some(Opaque(""))
            } else {
              url.host = Some(host)
            }
          }
        }
      }
      SpecialRelativeOrAuthority | Relative => {
        // At EOF in these states, copy from base URL
        guard base is Some(base) else {
          abort("base must be Some(base) in Relative-like state at EOF")
        }
        url.username = base.username
        url.password = base.password
        url.host = base.host
        url.port = base.port
        url.path = base.path.clone()
        url.query = base.query
      }
      RelativeSlash => {
        // At EOF in RelativeSlash (saw "/" but nothing after), set root path
        guard base is Some(base) else {
          abort("base must be Some(base) in RelativeSlash state at EOF")
        }
        url.username = base.username
        url.password = base.password
        url.host = base.host
        url.port = base.port
        // Don't copy path - "/" means root path, so use empty segments (serializes as "/")
        // Don't copy query either
      }
      File =>
        // At EOF in File state (e.g., "file:" alone)
        // Copy from base if available per WHATWG spec
        if base is Some(base) && base.scheme == "file" {
          url.host = base.host
          url.path = base.path.clone()
          url.query = base.query
          // fragment is NOT copied per spec
        } else {
          url.host = Some(Opaque(""))
          // path defaults to Segments([]) which serializes as "/" for file URLs
        }
      FileSlash => {
        // At EOF in FileSlash state (e.g., "/" with file base)
        url.scheme = "file"
        // Preserve host and Windows drive letter from base if present
        if base is Some(base) && base.scheme == "file" {
          url.host = base.host
          match base.path {
            Segments([drive, ..]) if drive.length() == 2 =>
              match drive {
                [letter, ':'] if letter.is_ascii_alphabetic() =>
                  // Include empty segment for trailing slash: C: + / = C:/
                  url.path = Segments([drive, ""])
                _ => ()
              }
            _ => ()
          }
        } else {
          url.host = Some(Opaque(""))
        }
      }
      PathOrAuthority =>
        // At EOF in PathOrAuthority state - we consumed the '/' after scheme
        // Set path to "/" for URLs like "foo:/"
        url.path = Segments([""])
      SpecialAuthoritySlashes | SpecialAuthorityIgnoreSlashes => {
        // At EOF waiting for authority - special URLs require host
        validation_errors.push(HostMissing)
        return None
      }
      _ => ()
    }
    break // Exit outer loop after successful EOF handling
  }
  Some(url)
}