///|
fn synthetic_navigation_is_html_data_url(url : String) -> Bool {
  if !url.has_prefix("data:") {
    return false
  }
  match parse_data_url(url) {
    Some((content_type, _)) =>
      content_type.has_prefix("text/html") ||
      content_type.has_prefix("text/xml") ||
      content_type.has_prefix("application/xml") ||
      content_type.has_prefix("application/xhtml+xml") ||
      content_type == ""
    None => false
  }
}

///|
fn synthetic_navigation_http_origin(url : String) -> String? {
  if url.has_prefix("http://") {
    return Some(extract_http_origin(url, "http://", 7))
  }
  if url.has_prefix("https://") {
    return Some(extract_http_origin(url, "https://", 8))
  }
  None
}

///|
fn synthetic_navigation_join_origin_path(
  origin : String,
  path : String,
) -> String {
  if origin.has_suffix("/") && path.has_prefix("/") {
    origin + path.unsafe_substring(start=1, end=path.length())
  } else {
    origin + path
  }
}

///|
fn synthetic_navigation_push_unique_resource(
  requests : Array[SyntheticNavigationRequestEntry],
  seen_urls : Map[String, Bool],
  context_id : String,
  resource_url : String,
  navigation : String?,
  destination : String,
  initiator_type : String?,
  proto : BidiProtocol,
) -> Unit {
  let cleaned = strip_wrapping_quotes_and_spaces(resource_url)
  if cleaned == "" || seen_urls.contains(cleaned) {
    return
  }
  seen_urls[cleaned] = true
  requests.push({
    context_id,
    url: cleaned,
    request_token: proto.next_synthetic_network_request_id(),
    redirect_count: 0,
    navigation,
    destination,
    initiator_type,
  })
}

///|
fn strip_wrapping_quotes_and_spaces(raw : String) -> String {
  let trimmed = raw.trim().to_owned()
  let chars = trimmed.to_array()
  if chars.length() >= 2 {
    let first = chars[0]
    let last = chars[chars.length() - 1]
    if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
      return trimmed.unsafe_substring(start=1, end=trimmed.length() - 1)
    }
  }
  trimmed
}

///|
fn BidiProtocol::collect_attribute_values(
  self : BidiProtocol,
  ctx_id : String,
  selector : String,
  attribute_name : String,
) -> Array[String] {
  match self.manager.get_session(ctx_id) {
    Some(session) => {
      let doc = session.get_tree().get_document()
      match session.get_tree().query_selector_all(doc, selector) {
        Ok(nodes) => {
          let values : Array[String] = []
          for node in nodes {
            match session.get_tree().get_attribute(node, attribute_name) {
              Ok(Some(value)) => values.push(value)
              _ => ()
            }
          }
          values
        }
        Err(_) => []
      }
    }
    None => []
  }
}

///|
fn BidiProtocol::collect_text_contents(
  self : BidiProtocol,
  ctx_id : String,
  selector : String,
) -> Array[String] {
  match self.manager.get_session(ctx_id) {
    Some(session) => {
      let doc = session.get_tree().get_document()
      match session.get_tree().query_selector_all(doc, selector) {
        Ok(nodes) => {
          let texts : Array[String] = []
          for node in nodes {
            match session.get_tree().get_text_content(node) {
              Ok(text) => texts.push(text)
              Err(_) => ()
            }
          }
          texts
        }
        Err(_) => []
      }
    }
    None => []
  }
}

///|
fn collect_css_import_urls(source : String) -> Array[String] {
  let urls : Array[String] = []
  let chars = source.to_array()
  let mut start = 0
  while start < source.length() {
    match find_substring(source, "@import", start) {
      Some(idx) => {
        let mut cursor = idx + 7
        while cursor < chars.length() &&
              (
                chars[cursor] == ' ' ||
                chars[cursor] == '\n' ||
                chars[cursor] == '\t' ||
                chars[cursor] == '\r'
              ) {
          cursor += 1
        }
        if cursor + 4 <= chars.length() {
          let marker = source.unsafe_substring(start=cursor, end=cursor + 4)
          if marker == "url(" {
            cursor += 4
            while cursor < chars.length() &&
                  (
                    chars[cursor] == ' ' ||
                    chars[cursor] == '\n' ||
                    chars[cursor] == '\t' ||
                    chars[cursor] == '\r'
                  ) {
              cursor += 1
            }
            let value_start = cursor
            while cursor < chars.length() && chars[cursor] != ')' {
              cursor += 1
            }
            if cursor <= chars.length() {
              let raw_value = source.unsafe_substring(
                start=value_start,
                end=cursor,
              )
              let cleaned = strip_wrapping_quotes_and_spaces(raw_value)
              if cleaned != "" {
                urls.push(cleaned)
              }
            }
          }
        }
        start = idx + 7
      }
      None => break
    }
  }
  urls
}

///|
fn collect_static_import_urls(source : String) -> Array[String] {
  collect_quoted_values_after_marker(source, "from", true)
}

///|
fn collect_dynamic_import_urls(source : String) -> Array[String] {
  let values : Array[String] = []
  let chars = source.to_array()
  let mut start = 0
  while start < source.length() {
    match find_substring(source, "import", start) {
      Some(idx) => {
        let mut cursor = idx + 6
        while cursor < chars.length() &&
              (
                chars[cursor] == ' ' ||
                chars[cursor] == '\n' ||
                chars[cursor] == '\t' ||
                chars[cursor] == '\r'
              ) {
          cursor += 1
        }
        if cursor < chars.length() && chars[cursor] == '(' {
          cursor += 1
          while cursor < chars.length() &&
                (
                  chars[cursor] == ' ' ||
                  chars[cursor] == '\n' ||
                  chars[cursor] == '\t' ||
                  chars[cursor] == '\r'
                ) {
            cursor += 1
          }
          if cursor < chars.length() &&
            (chars[cursor] == '"' || chars[cursor] == '\'') {
            let quote = chars[cursor]
            let value_start = cursor + 1
            cursor += 1
            while cursor < chars.length() && chars[cursor] != quote {
              cursor += 1
            }
            if cursor <= chars.length() {
              let value = source.unsafe_substring(start=value_start, end=cursor)
              if value != "" {
                values.push(value)
              }
            }
          }
        }
        start = idx + 6
      }
      None => break
    }
  }
  values
}

///|
fn collect_quoted_values_after_marker(
  source : String,
  marker : String,
  allow_whitespace : Bool,
) -> Array[String] {
  let values : Array[String] = []
  let chars = source.to_array()
  let mut start = 0
  while start < source.length() {
    match find_substring(source, marker, start) {
      Some(idx) => {
        let mut cursor = idx + marker.length()
        if allow_whitespace {
          while cursor < chars.length() &&
                (
                  chars[cursor] == ' ' ||
                  chars[cursor] == '\n' ||
                  chars[cursor] == '\t' ||
                  chars[cursor] == '\r'
                ) {
            cursor += 1
          }
        }
        if cursor < chars.length() &&
          (chars[cursor] == '"' || chars[cursor] == '\'') {
          let quote = chars[cursor]
          let value_start = cursor + 1
          cursor += 1
          while cursor < chars.length() && chars[cursor] != quote {
            cursor += 1
          }
          if cursor <= chars.length() {
            let value = source.unsafe_substring(start=value_start, end=cursor)
            if value != "" {
              values.push(value)
            }
          }
        }
        start = idx + marker.length()
      }
      None => break
    }
  }
  values
}