// URI parser implementation following RFC 3986

///|
pub enum Host {
  IPv6Address(@bytes.View)
  RegName(@bytes.View)
} derive(Eq, Show)

///|
pub struct Authority {
  mut userinfo : @bytes.View?
  mut host : Host
  mut port : Int?
} derive(Eq, Show)

///|
pub struct Uri {
  mut scheme : @bytes.View?
  mut authority : Authority?
  path : Array[@bytes.View]
  mut query : @bytes.View?
  mut fragment : @bytes.View?
} derive(Eq, Show)

///|
pub suberror ParseError {
  InvalidScheme(@bytes.View)
  InvalidHeirPart(@bytes.View)
  InvalidPercentEncoding(@bytes.View)
  MissingColon
  MissingScheme
  InvalidSegment(@bytes.View)
  InvalidSchemeOrSegment(@bytes.View)
  Invalid(@bytes.View)
}

///|
fn @bytes.View::slice_until(
  self : @bytes.View,
  other : @bytes.View,
) -> @bytes.View {
  self.data()[self.start_offset():other.start_offset()]
}

// RFC 3986 Character Classification Functions

///|
fn is_alpha_byte(b : Byte) -> Bool {
  (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
}

///|
fn is_digit_byte(b : Byte) -> Bool {
  b >= '0' && b <= '9'
}

///|
fn is_hexdig_byte(b : Byte) -> Bool {
  is_digit_byte(b) || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')
}

///|
fn is_unreserved_byte(b : Byte) -> Bool {
  is_alpha_byte(b) ||
  is_digit_byte(b) ||
  b == '-' ||
  b == '.' ||
  b == '_' ||
  b == '~'
}

///|
fn is_gen_delim_byte(b : Byte) -> Bool {
  b == ':' ||
  b == '/' ||
  b == '?' ||
  b == '#' ||
  b == '[' ||
  b == ']' ||
  b == '@'
}

///|
fn is_sub_delim_byte(b : Byte) -> Bool {
  b == '!' ||
  b == '$' ||
  b == '&' ||
  b == '\'' ||
  b == '(' ||
  b == ')' ||
  b == '*' ||
  b == '+' ||
  b == ',' ||
  b == ';' ||
  b == '='
}

///|
fn is_pchar_byte(b : Byte) -> Bool {
  is_unreserved_byte(b) || is_sub_delim_byte(b) || b == ':' || b == '@'
}

// RFC 3986 Parsing Functions

// URI-reference = scheme ":" "//" authority path-abempty [ "?" query ] [ "#" fragment ]
//               / scheme ":" "/" [ segment-nz *( "/" segment ) ] [ "?" query ] [ "#" fragment ]
//               / scheme ":" segment-nz *( "/" segment ) [ "?" query ] [ "#" fragment ]
//               / scheme ":" [ "?" query ] [ "#" fragment ]
//               / "//" authority path-abempty [ "?" query ] [ "#" fragment ]
//               / "/" [ segment-nz *( "/" segment ) ] [ "?" query ] [ "#" fragment ]
//               / segment-nz-nc *( "/" segment ) [ "?" query ] [ "#" fragment ]
//               / [ "?" query ] [ "#" fragment ]

///|
fn parse_scheme_or_segment_nz_nc(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let s = source
  let segment = s
  loop s {
    [':', .. k] as s => {
      uri.scheme = Some(source.slice_until(s))
      parse_hier_part_then_query(k, uri)
    }
    ['/', .. k] as s => {
      uri.path.push(segment.slice_until(s))
      parse_path_abempty_then_query(k, uri)
    }
    ['?', .. k] as s => {
      uri.path.push(segment.slice_until(s))
      parse_query_then_fragment(k, uri)
    }
    ['#', .. k] as s => {
      uri.path.push(segment.slice_until(s))
      parse_query_then_fragment(k, uri)
    }
    [b, .. k] if is_unreserved_byte(b) || is_sub_delim_byte(b) || b == '@' =>
      continue k
    _ => raise ParseError::InvalidSchemeOrSegment(source)
  }
}

///|
fn parse_uri_reference(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    [.. "//", .. k] => {
      // relative-ref > relative-part > "//" authority path-abempty
      let k = parse_authority(k, uri)
      parse_path_abempty_then_query(k, uri)
    }
    ['/', .. k] =>
      // relative-ref > relative-part > "/" [ segment-nz *( "/" segment ) ] [ "?" query ] [ "#" fragment ]
      parse_path_abempty_then_query(k, uri)
    ['?', .. k] =>
      // relative-ref > relative-part > "?" query
      parse_query_then_fragment(k, uri)
    ['#', .. k] =>
      // relative-ref > relative-part > "#" fragment
      parse_fragment(k, uri)
    [..] as s => parse_scheme_or_segment_nz_nc(s, uri)
  }
}

///|
pub fn Uri::parse(source : @bytes.View) -> Uri raise ParseError {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  let source = parse_uri_reference(source, uri)
  if !(source is []) {
    raise ParseError::Invalid(source)
  }
  uri
}

///|
fn parse_scheme(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    [b, .. k] if is_alpha_byte(b) =>
      loop k {
        [b, .. s] if is_alpha_byte(b) ||
          is_digit_byte(b) ||
          b == '+' ||
          b == '-' ||
          b == '.' => continue s
        [':', .. k] as s => {
          uri.scheme = Some(source.slice_until(s))
          return k
        }
        [_, ..] as s => raise ParseError::InvalidScheme(s)
        [] => raise ParseError::MissingColon
      }
    [_, ..] as s => raise ParseError::InvalidScheme(s)
    [] => raise ParseError::MissingScheme
  }
}

///|
fn parse_percent_encoding(source : @bytes.View) -> @bytes.View raise ParseError {
  match source {
    [
      '0'..='9'
      | 'A'..='Z'
      | 'a'..='z',
      '0'..='9'
      | 'A'..='Z'
      | 'a'..='z',
      .. source,
    ] => source
    [..] => raise ParseError::InvalidPercentEncoding(source)
  }
}

///|
fn parse_userinfo(
  source : @bytes.View,
  authority : Authority,
) -> @bytes.View raise ParseError {
  let start = source
  loop source {
    ['@', .. k] as s => {
      authority.userinfo = Some(start.slice_until(s))
      return k
    }
    ['%', .. k] => continue parse_percent_encoding(k)
    [b, .. k] if is_unreserved_byte(b) || is_sub_delim_byte(b) || b == ':' =>
      continue k
    [_, ..] as s => raise ParseError::InvalidHeirPart(s)
    [] as s => return s
  }
}

///|
fn parse_dec_octet(source : @bytes.View) -> (@bytes.View, Int) raise ParseError {
  match source {
    ['2', '5', d, .. k] if d >= '0' && d <= '5' => (k, 250 + (d.to_int() - '0'))
    ['2', d1, d2, .. k] if d1 >= '0' && d1 <= '4' && is_digit_byte(d2) =>
      (k, 200 + (d1.to_int() - '0') * 10 + (d2.to_int() - '0'))
    ['1', d1, d2, .. k] if is_digit_byte(d1) && is_digit_byte(d2) =>
      (k, 100 + (d1.to_int() - '0') * 10 + (d2.to_int() - '0'))
    [d1, d2, .. k] if d1 >= '1' && d1 <= '9' && is_digit_byte(d2) =>
      (k, (d1.to_int() - '0') * 10 + (d2.to_int() - '0'))
    [d, .. k] if is_digit_byte(d) => (k, d.to_int() - '0')
    _ => raise ParseError::InvalidHeirPart(source)
  }
}

///|
fn parse_ipv4_address(
  source : @bytes.View,
) -> (@bytes.View, @bytes.View) raise ParseError {
  let start = source
  let (s1, _) = parse_dec_octet(source)
  match s1 {
    ['.', .. s2] => {
      let (s3, _) = parse_dec_octet(s2)
      match s3 {
        ['.', .. s4] => {
          let (s5, _) = parse_dec_octet(s4)
          match s5 {
            ['.', .. s6] => {
              let (s7, _) = parse_dec_octet(s6)
              (s7, start.slice_until(s7))
            }
            _ => raise ParseError::InvalidHeirPart(source)
          }
        }
        _ => raise ParseError::InvalidHeirPart(source)
      }
    }
    _ => raise ParseError::InvalidHeirPart(source)
  }
}

///|
fn parse_reg_name(
  source : @bytes.View,
  authority : Authority,
) -> @bytes.View raise ParseError {
  let s = source
  loop s {
    ['%', ..] as s => continue parse_percent_encoding(s)
    [b, .. k] if is_unreserved_byte(b) || is_sub_delim_byte(b) => continue k
    _ as s => {
      authority.host = Host::RegName(source.slice_until(s))
      s
    }
  }
}

///|
fn parse_ipv6_address(
  source : @bytes.View,
  authority : Authority,
) -> @bytes.View raise ParseError {
  // Simplified IPv6 parsing - match until ']'
  let s = source
  loop s {
    [']', .. k] as s => {
      authority.host = IPv6Address(source.slice_until(s))
      k
    }
    [b, .. s] if is_hexdig_byte(b) || b == ':' || b == '.' => continue s
    _ as s => raise ParseError::InvalidHeirPart(s)
  }
}

///|
fn parse_host(
  source : @bytes.View,
  authority : Authority,
) -> @bytes.View raise ParseError {
  // Try IP literal first
  match source {
    ['[', .. k] => parse_ipv6_address(k, authority)
    _ =>
      // Try IPv4 address, fallback to reg-name
      parse_reg_name(source, authority)
  }
}

///|
fn parse_port(source : @bytes.View, authority : Authority) -> @bytes.View {
  match source {
    [':', .. k] => {
      let mut port_val = 0
      loop k {
        [d, .. s] if is_digit_byte(d) => {
          port_val = port_val * 10 + (d.to_int() - '0')
          continue s
        }
        _ as s => {
          authority.port = Some(port_val)
          return s
        }
      }
    }
    _ => source
  }
}

///|
fn parse_authority(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let authority = Authority::{ userinfo: None, host: RegName(""), port: None }
  uri.authority = Some(authority)

  // Try to parse userinfo first
  let remaining = parse_userinfo(source, authority) catch {
    _ => source // No userinfo, continue with host
  }
  let remaining = parse_host(remaining, authority)
  parse_port(remaining, authority)
}

///|
fn parse_segment(
  source : @bytes.View,
) -> (@bytes.View, @bytes.View) raise ParseError {
  let start = source
  loop source {
    ['%', .. k] => continue parse_percent_encoding(k)
    [b, .. k] if is_pchar_byte(b) => continue k
    _ as s => (s, start.slice_until(s))
  }
}

///| Continuation-based completion functions
fn parse_done(source : @bytes.View, uri : Uri) -> @bytes.View {
  source
}

///|
fn parse_fragment(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let s = source
  loop s {
    ['%', ..] as s => continue parse_percent_encoding(s)
    [b, .. k] if is_pchar_byte(b) || b == '/' || b == '?' => continue k
    [..] as s => {
      uri.fragment = Some(source.slice_until(s))
      return s
    }
  }
}

///|
fn parse_query_then_fragment(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let start = source
  loop source {
    ['#', .. k] as s => {
      uri.query = Some(start.slice_until(s))
      parse_fragment(k, uri)
    }
    ['%', ..] as s => continue parse_percent_encoding(s)
    [b, .. k] if is_pchar_byte(b) || b == '/' || b == '?' => continue k
    [..] as s => {
      uri.query = Some(start.slice_until(s))
      s
    }
  }
}

///|
fn parse_path_abempty_then_query(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let s = source
  loop s {
    ['%', .. k] => continue parse_percent_encoding(k)
    ['/', .. k] as s => {
      uri.path.push(source.slice_until(s))
      parse_path_abempty_then_query(k, uri)
    }
    ['?', .. k] as s => {
      uri.path.push(source.slice_until(s))
      parse_query_then_fragment(k, uri)
    }
    ['#', .. k] as s => {
      uri.path.push(source.slice_until(s))
      parse_fragment(k, uri)
    }
    [b, .. k] if is_pchar_byte(b) => continue k
    [] as s => {
      uri.path.push(source.slice_until(s))
      s
    }
    [..] as s => raise ParseError::InvalidSegment(s)
  }
}

///|
fn parse_hier_part_then_query(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    [.. "//", .. k] =>
      match parse_authority(k, uri) {
        ['/', .. k] => parse_path_abempty_then_query(k, uri)
        ['?', .. k] => parse_query_then_fragment(k, uri)
        ['#', ..] => parse_fragment(k, uri)
        [] => parse_done(k, uri)
        [..] as s => raise ParseError::Invalid(s)
      }
    _ => parse_path_abempty_then_query(source, uri)
  }
}

///|
fn parse_path_noscheme_then_query(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let (remaining, segment_val) = parse_segment_nz_nc(source)
  uri.path.push(segment_val)
  parse_path_abempty_then_query(remaining, uri)
}

///|
fn parse_segment_nz_nc(
  source : @bytes.View,
) -> (@bytes.View, @bytes.View) raise ParseError {
  let start = source
  let mut has_content = false
  let s = source
  loop s {
    ['%', .. k] => {
      let k = parse_percent_encoding(k)
      has_content = true
      continue k
    }
    [b, .. k] if is_unreserved_byte(b) || is_sub_delim_byte(b) || b == '@' => {
      has_content = true
      continue k
    }
    _ as s => if has_content { (s, start.slice_until(s)) } else { (source, "") }
  }
}

///|
fn parse_relative_part_then_query(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    [.. "//", .. k] => {
      let remaining = parse_authority(k, uri)
      parse_path_abempty_then_query(remaining, uri)
    }
    ['/', .. k] => parse_path_abempty_then_query(k, uri)
    ['?', .. k] => parse_query_then_fragment(k, uri)
    ['#', ..] => parse_fragment(source, uri)
    [] => parse_done(source, uri)
    _ => parse_path_noscheme_then_query(source, uri)
  }
}

///|
fn parse_relative_ref(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  uri.scheme = None
  parse_relative_part_then_query(source, uri)
}

///|
fn parse_absolute_uri(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let remaining = parse_scheme(source, uri) // already consumes the ':'
  parse_hier_part_then_query_no_fragment(remaining, uri)
}

///|
fn parse_hier_part_then_query_no_fragment(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    [.. "//", .. k] => {
      let remaining = parse_authority(k, uri)
      parse_path_abempty_then_query_no_fragment(remaining, uri)
    }
    _ => parse_path_abempty_then_query_no_fragment(source, uri)
  }
}

///|
fn parse_path_abempty_then_query_no_fragment(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let s = source
  loop s {
    ['/', .. k] => {
      let (remaining, segment_val) = parse_segment(k)
      uri.path.push(segment_val)
      continue remaining
    }
    _ as s => parse_query_no_fragment_or_done(s, uri)
  }
}

///|
fn parse_query_no_fragment_or_done(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  match source {
    ['?', .. k] => parse_query_no_fragment_then_done(k, uri)
    _ => parse_done(source, uri)
  }
}

///|
fn parse_query_no_fragment_then_done(
  source : @bytes.View,
  uri : Uri,
) -> @bytes.View raise ParseError {
  let start = source
  loop source {
    ['%', .. k] => continue parse_percent_encoding(k)
    [b, .. k] if is_pchar_byte(b) || b == '/' || b == '?' => continue k
    _ as s => {
      uri.query = Some(start.slice_until(s))
      parse_done(s, uri)
    }
  }
}

///|
fn remove_dot_segments(path : Array[@bytes.View]) -> Array[@bytes.View] {
  let output = Array::new()
  for segment in path {
    if segment == "." {
      continue // Remove single dot segments
    } else if segment == ".." {
      if output.length() > 0 {
        output.pop() |> ignore() // Remove last segment and the .. segment
      }
      // If output is empty, ignore the .. segment
    } else {
      output.push(segment)
    }
  }
  output
}

///|
test "RFC 3986 URI Reference Parsing" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(
    parse_uri_reference(
      "http://example.com/path/to/resource?query=parameter#fragment", uri,
    ),
    "",
  )
  assert_eq(uri.scheme, Some("http"))
  assert_eq(
    uri.authority,
    Some(Authority::{ userinfo: None, host: RegName("example.com"), port: None }),
  )
  assert_eq(uri.path, ["path", "to", "resource"])
  assert_eq(uri.query, Some("query=parameter"))
  assert_eq(uri.fragment, Some("fragment"))
}

///|
test "RFC 3986 Authority with Userinfo and Port" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(
    parse_uri_reference("ftp://user:pass@ftp.example.com:21/path", uri),
    "",
  )
  assert_eq(uri.scheme, Some("ftp"))
  assert_eq(
    uri.authority,
    Some(Authority::{
      userinfo: Some("user:pass"),
      host: RegName("ftp.example.com"),
      port: Some(21),
    }),
  )
  assert_eq(uri.path, ["path"])
}

///|
test "RFC 3986 IPv6 Literal" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("http://[2001:db8::1]:8080/", uri), "")
  assert_eq(uri.scheme, Some("http"))
  assert_eq(
    uri.authority,
    Some(Authority::{
      userinfo: None,
      host: IPv6Address("2001:db8::1"),
      port: Some(8080),
    }),
  )
  assert_eq(uri.path, [])
}

///|
test "RFC 3986 Percent Encoding Preservation" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("http://example.com/hello%20world", uri), "")
  assert_eq(uri.scheme, Some("http"))
  assert_eq(uri.path, ["hello%20world"]) // preserved, not decoded
}

///|
test "RFC 3986 Relative Reference" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("../path/file.txt", uri), "")
  assert_eq(uri.scheme, None)
  assert_eq(uri.authority, None)
  assert_eq(uri.path, ["..", "path", "file.txt"])
}

///|
test "RFC 3986 Absolute Path" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("/absolute/path", uri), "")
  assert_eq(uri.scheme, None)
  assert_eq(uri.authority, None)
  assert_eq(uri.path, ["absolute", "path"])
}

///|
test "RFC 3986 Query Only" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("?key=value&foo=bar", uri), "")
  assert_eq(uri.scheme, None)
  assert_eq(uri.authority, None)
  assert_eq(uri.path, [])
  assert_eq(uri.query, Some("key=value&foo=bar"))
}

///|
test "RFC 3986 Fragment Only" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  assert_eq(parse_uri_reference("#section1", uri), "")
  assert_eq(uri.scheme, None)
  assert_eq(uri.authority, None)
  assert_eq(uri.path, [])
  assert_eq(uri.fragment, Some("section1"))
}

///|
test "RFC 3986 Character Classification" {
  // Test unreserved characters
  assert_eq(is_unreserved_byte('A'), true)
  assert_eq(is_unreserved_byte('z'), true)
  assert_eq(is_unreserved_byte('0'), true)
  assert_eq(is_unreserved_byte('-'), true)
  assert_eq(is_unreserved_byte('.'), true)
  assert_eq(is_unreserved_byte('_'), true)
  assert_eq(is_unreserved_byte('~'), true)
  assert_eq(is_unreserved_byte('!'), false)

  // Test gen-delims
  assert_eq(is_gen_delim_byte(':'), true)
  assert_eq(is_gen_delim_byte('/'), true)
  assert_eq(is_gen_delim_byte('?'), true)
  assert_eq(is_gen_delim_byte('#'), true)
  assert_eq(is_gen_delim_byte('['), true)
  assert_eq(is_gen_delim_byte(']'), true)
  assert_eq(is_gen_delim_byte('@'), true)

  // Test sub-delims
  assert_eq(is_sub_delim_byte('!'), true)
  assert_eq(is_sub_delim_byte('$'), true)
  assert_eq(is_sub_delim_byte('&'), true)
  assert_eq(is_sub_delim_byte('\''), true)
  assert_eq(is_sub_delim_byte('('), true)
  assert_eq(is_sub_delim_byte(')'), true)
  assert_eq(is_sub_delim_byte('*'), true)
  assert_eq(is_sub_delim_byte('+'), true)
  assert_eq(is_sub_delim_byte(','), true)
  assert_eq(is_sub_delim_byte(';'), true)
  assert_eq(is_sub_delim_byte('='), true)
}

///|
test "RFC 3986 Dot Segment Removal" {
  let path1 : Array[@bytes.View] = ["a", "b", "c", ".", "..", "..", "g"]
  let result1 = remove_dot_segments(path1)
  assert_eq(result1, ["a", "g"])
  let path2 : Array[@bytes.View] = ["..", "..", "a", "b"]
  let result2 = remove_dot_segments(path2)
  assert_eq(result2, ["a", "b"])
  let path3 : Array[@bytes.View] = [".", "a", ".", "b", "."]
  let result3 = remove_dot_segments(path3)
  assert_eq(result3, ["a", "b"])
}

///|
test "RFC 3986 Absolute URI Parsing" {
  let uri = Uri::{
    scheme: None,
    authority: None,
    path: [],
    query: None,
    fragment: None,
  }
  inspect(
    parse_absolute_uri(
      "http://example.com/path/to/resource?query=parameter", uri,
    ),
    content="",
  )
  assert_eq(uri.scheme, Some("http"))
  assert_eq(
    uri.authority,
    Some(Authority::{ userinfo: None, host: RegName("example.com"), port: None }),
  )
  assert_eq(uri.path, ["path", "to", "resource"])
  assert_eq(uri.query, Some("query=parameter"))
  assert_eq(uri.fragment, None)
}