///|
fn is_safe_asset_path(path : String) -> Bool {
  if path == "" || path.has_prefix("/") || path.contains("\\") {
    return false
  }
  let mut component = ""
  for character in path {
    if character == '/' {
      if !is_safe_path_component(component) {
        return false
      }
      component = ""
    } else {
      if character == ':' || character == '\u0000' {
        return false
      }
      component += character.to_string()
    }
  }
  is_safe_path_component(component)
}

///|
fn is_safe_path_component(component : String) -> Bool {
  component != "" && component != "." && component != ".."
}

///|
fn join_asset_path(root : String, relative_path : String) -> String {
  let separator = if root.contains("\\") { "\\" } else { "/" }
  let mut normalized_path = ""
  for character in relative_path {
    if character == '/' {
      normalized_path += separator
    } else {
      normalized_path += character.to_string()
    }
  }
  join_path(root, normalized_path)
}

///|
async fn collect_asset_files(root : String) -> Array[String] {
  let directories : Array[String] = [""]
  let files : Array[String] = []
  let mut directory_index = 0
  while directory_index < directories.length() {
    let relative_directory = directories[directory_index]
    let directory_path = if relative_directory == "" {
      root
    } else {
      join_asset_path(root, relative_directory)
    }
    let entries = @fs.readdir(directory_path, sort=true)
    for entry in entries {
      let relative_path = if relative_directory == "" {
        entry
      } else {
        relative_directory + "/" + entry
      }
      let full_path = join_asset_path(root, relative_path)
      match @fs.kind(full_path, follow_symlink=false) {
        @fs.FileKind::Directory => directories.push(relative_path)
        @fs.FileKind::Regular | @fs.FileKind::SymLink =>
          files.push(relative_path)
        _ => ()
      }
    }
    directory_index += 1
  }
  files
}

///|
fn contains_entry(entries : Array[String], expected : String) -> Bool {
  for entry in entries {
    if entry == expected {
      return true
    }
  }
  false
}

///|
fn join_path(root : String, name : String) -> String {
  if root.has_suffix("/") || root.has_suffix("\\") {
    root + name
  } else if root.contains("\\") {
    root + "\\" + name
  } else {
    root + "/" + name
  }
}