///|
pub fn normalize_relative_path(input : String) -> String {
  let text = input.replace_all(old="\\", new="/")
  let pieces = text.split("/")
  let stack : Array[String] = []
  for piece in pieces {
    if piece == "" || piece == "." {
      continue
    }
    if piece == ".." {
      if !stack.is_empty() {
        stack.pop() |> ignore
      }
    } else {
      stack.push(piece.to_owned())
    }
  }
  stack.join("/")
}

///|
pub fn path_depth(path : String) -> Int {
  let normalized = normalize_relative_path(path)
  if normalized == "" {
    0
  } else {
    normalized.split("/").length()
  }
}

///|
pub fn basename(path : String) -> String {
  let normalized = normalize_relative_path(path)
  if normalized == "" {
    ""
  } else {
    let parts = normalized.split("/").to_array()
    parts[parts.length() - 1].to_owned()
  }
}

///|
pub fn parent_path(path : String) -> String {
  let normalized = normalize_relative_path(path)
  let parts = normalized.split("/").to_array()
  if parts.length() <= 1 {
    ""
  } else {
    parts[:parts.length() - 1].join("/")
  }
}

///|
pub fn extension_of(path : String) -> String {
  let name = basename(path)
  let bytes : Array[Char] = [..name]
  let mut dot = -1
  for i = 0; i < bytes.length(); i = i + 1 {
    if bytes[i] == '.' {
      dot = i
    }
  }
  if dot <= 0 || dot == bytes.length() - 1 {
    ""
  } else {
    let suffix : String = [..bytes[dot + 1:]]
    suffix.to_lower()
  }
}

///|
pub fn is_hidden_path(path : String) -> Bool {
  for part in normalize_relative_path(path).split("/") {
    if part.length() > 1 && part.has_prefix(".") {
      return true
    }
  }
  false
}

///|
pub fn is_descendant(path : String, parent : String) -> Bool {
  let child = normalize_relative_path(path)
  let base = normalize_relative_path(parent)
  base == "" || child.has_prefix(base + "/")
}

///|
fn wildcard_match_here(
  pattern : Array[Char],
  text : Array[Char],
  pi : Int,
  ti : Int,
) -> Bool {
  if pi == pattern.length() {
    return ti == text.length()
  }
  if pattern[pi] == '*' {
    let mut next = ti
    while next <= text.length() {
      if wildcard_match_here(pattern, text, pi + 1, next) {
        return true
      }
      next += 1
    }
    false
  } else if ti < text.length() &&
    (pattern[pi] == '?' || pattern[pi] == text[ti]) {
    wildcard_match_here(pattern, text, pi + 1, ti + 1)
  } else {
    false
  }
}

///|
pub fn wildcard_match(pattern : String, text : String) -> Bool {
  wildcard_match_here([..pattern], [..text], 0, 0)
}

///|
pub fn matches_any_pattern(path : String, patterns : Array[String]) -> Bool {
  let normalized = normalize_relative_path(path)
  for pattern in patterns {
    if wildcard_match(normalize_relative_path(pattern), normalized) ||
      wildcard_match(normalize_relative_path(pattern), basename(normalized)) {
      return true
    }
  }
  false
}