///|
pub fn clean_path(path : String) -> String {
  let normalized = normalize_path(url_path_without_query(path))
  let parts = split_char(normalized, '/')
  let stack : Array[String] = []
  for part in parts {
    if part == "" || part == "." {
      continue
    } else if part == ".." {
      if !stack.is_empty() {
        ignore(stack.pop())
      }
    } else {
      stack.push(part)
    }
  }
  "/" + join_with(stack, "/")
}

///|
pub fn path_depth(path : String) -> Int {
  let clean = clean_path(path)
  if clean == "/" {
    return 0
  }
  let mut depth = 0
  for part in split_char(clean, '/') {
    if part != "" {
      depth += 1
    }
  }
  depth
}

///|
pub fn path_parent(path : String) -> String {
  let clean = clean_path(path)
  if clean == "/" {
    return "/"
  }
  let parts = split_char(clean, '/')
  let kept : Array[String] = []
  for index = 0; index < parts.length() - 1; index = index + 1 {
    if parts[index] != "" {
      kept.push(parts[index])
    }
  }
  if kept.is_empty() {
    "/"
  } else {
    "/" + join_with(kept, "/")
  }
}

///|
pub fn path_basename(path : String) -> String {
  let clean = clean_path(path)
  if clean == "/" {
    return ""
  }
  let parts = split_char(clean, '/')
  for index = parts.length() - 1; index >= 0; index = index - 1 {
    if parts[index] != "" {
      return parts[index]
    }
  }
  ""
}

///|
pub fn path_extension(path : String) -> String {
  let name = path_basename(path)
  match name.find(".") {
    Some(_) => {
      let mut ext = ""
      let mut seen_dot = false
      for _, ch in name {
        if ch == '.' {
          seen_dot = true
          ext = ""
        } else if seen_dot {
          ext = ext + [ch]
        }
      }
      ext
    }
    None => ""
  }
}

///|
pub fn path_has_extension(path : String, ext : String) -> Bool {
  lower_ascii(path_extension(path)) == lower_ascii(ext)
}

///|
pub fn remove_duplicate_paths(paths : Array[String]) -> Array[String] {
  let result : Array[String] = []
  for path in paths {
    let clean = clean_path(path)
    if !contains_string(result, clean) {
      result.push(clean)
    }
  }
  result
}

///|
pub fn common_path_prefix(paths : Array[String]) -> String {
  if paths.is_empty() {
    return "/"
  }
  let first = clean_path(paths[0])
  let mut prefix = first
  for index = 1; index < paths.length(); index = index + 1 {
    prefix = common_two_path_prefix(prefix, clean_path(paths[index]))
  }
  prefix
}

///|
pub fn common_two_path_prefix(left : String, right : String) -> String {
  let a = split_char(clean_path(left), '/')
  let b = split_char(clean_path(right), '/')
  let kept : Array[String] = []
  let mut index = 0
  while index < a.length() && index < b.length() {
    if a[index] != "" && a[index] == b[index] {
      kept.push(a[index])
    } else if a[index] != "" || b[index] != "" {
      break
    }
    index += 1
  }
  if kept.is_empty() {
    "/"
  } else {
    "/" + join_with(kept, "/")
  }
}

///|
pub fn path_score(path : String) -> Int {
  let clean = clean_path(path)
  clean.length() + path_depth(clean) * 10
}

///|
pub fn sort_paths_by_depth(paths : Array[String]) -> Array[String] {
  let result = remove_duplicate_paths(paths)
  for i = 0; i < result.length(); i = i + 1 {
    for j = i + 1; j < result.length(); j = j + 1 {
      if path_score(result[j]) < path_score(result[i]) {
        let tmp = result[i]
        result[i] = result[j]
        result[j] = tmp
      }
    }
  }
  result
}

///|
pub fn classify_path(path : String) -> String {
  let clean = clean_path(path)
  if clean == "/" {
    "root"
  } else if path_has_extension(clean, "html") ||
    path_has_extension(clean, "htm") {
    "page"
  } else if path_has_extension(clean, "xml") {
    "xml"
  } else if path_has_extension(clean, "json") {
    "json"
  } else if path_has_extension(clean, "png") ||
    path_has_extension(clean, "jpg") ||
    path_has_extension(clean, "jpeg") ||
    path_has_extension(clean, "gif") {
    "image"
  } else if clean.has_suffix("/") {
    "directory"
  } else {
    "resource"
  }
}