///|
/// Minimal POSIX path handling. The Compose spec resolves every relative path
/// against the directory holding the compose file, so these are used a lot and
/// are kept independent of the process working directory.

///|
/// Expand a leading `~` into the user's home directory.
pub fn path_expand_home(path : String) -> String {
  if path == "~" {
    return home_dir()
  }
  if path.has_prefix("~/") {
    return home_dir() + "/" + path[2:].to_owned()
  }
  path
}

///|
/// The user's home directory, or an empty string.
pub fn home_dir() -> String {
  match @env.get_env_var("HOME") {
    Some(home) if home.length() > 0 => home
    _ =>
      match @env.get_env_var("USERPROFILE") {
        Some(profile) => profile
        None => ""
      }
  }
}

///|
/// Join two path fragments with a single separator.
pub fn path_join(a : String, b : String) -> String {
  if a.length() == 0 {
    return b
  }
  if b.length() == 0 {
    return a
  }
  if a.has_suffix("/") {
    return a + b
  }
  a + "/" + b
}

///|
/// Collapse `.` and `..` segments, preserving a leading `/`.
pub fn path_normalize(path : String) -> String {
  let absolute = path.has_prefix("/")
  let parts = path.split("/").to_array()
  let stack : Array[String] = []
  for part in parts {
    let seg = part.to_owned()
    if seg.length() == 0 || seg == "." {
      continue
    }
    if seg == ".." {
      if stack.length() > 0 && stack[stack.length() - 1] != ".." {
        ignore(stack.pop())
      } else if !absolute {
        stack.push("..")
      }
      continue
    }
    stack.push(seg)
  }
  let joined = stack.join("/")
  if absolute {
    "/" + joined
  } else if joined.length() == 0 {
    "."
  } else {
    joined
  }
}

///|
/// Resolve `path` against `base` when it is relative, then normalize.
pub fn path_abspath(base : String, path : String) -> String {
  let expanded = path_expand_home(path)
  if expanded.has_prefix("/") {
    path_normalize(expanded)
  } else {
    path_normalize(path_join(base, expanded))
  }
}

///|
/// The directory part of a path.
pub fn path_dirname(path : String) -> String {
  match path.rev_split_once("/") {
    Some(pair) => if pair.0.length() == 0 { "/" } else { pair.0.to_owned() }
    None => "."
  }
}

///|
/// The final component of a path.
pub fn path_basename(path : String) -> String {
  match path.rev_split_once("/") {
    Some(pair) => pair.1.to_owned()
    None => path
  }
}

///|
/// The process working directory, or `.` when it cannot be read.
pub fn current_dir() -> String {
  match @env.current_dir() {
    Some(dir) => dir
    None => "."
  }
}

///|
/// An absolute version of `path` relative to the current directory.
pub fn abspath(path : String) -> String {
  path_abspath(current_dir(), path)
}