///|
/// Returns whether `path` is an absolute Unix-style path.
///
/// Drive letters and UNC paths are intentionally left to a platform adapter.
pub fn path_is_absolute(path : String) -> Bool {
  path.has_prefix("/")
}

///|
/// Returns whether `path` is exactly `~` or starts with `~/`.
pub fn path_has_home_prefix(path : String) -> Bool {
  path == "~" || path.has_prefix("~/")
}

///|
/// Normalize portable Unix-style path segments without consulting a filesystem.
///
/// Leading `..` segments in a relative path are retained. This deliberately
/// does not resolve symlinks; adapters provide canonical identities for that.
pub fn normalize_path(path : String) -> String {
  let absolute = path_is_absolute(path)
  let segments : Array[String] = []
  for component in path.split("/") {
    let part = component.to_owned()
    if part == "" || part == "." {
      continue
    }
    if part == ".." {
      if !segments.is_empty() && segments[segments.length() - 1] != ".." {
        ignore(segments.pop())
      } else if !absolute {
        segments.push(part)
      }
    } else {
      segments.push(part)
    }
  }
  let body = segments.join("/")
  if absolute {
    if body == "" {
      "/"
    } else {
      "/\{body}"
    }
  } else if body == "" {
    "."
  } else {
    body
  }
}

///|
/// Return the normalized parent directory of a display path.
pub fn parent_directory(path : String) -> String {
  let normalized = normalize_path(path)
  if normalized == "/" || normalized == "." {
    normalized
  } else {
    match normalized.rev_find("/") {
      Some(index) =>
        if index == 0 {
          "/"
        } else {
          normalized[:index].to_owned()
        }
      None => "."
    }
  }
}

///|
/// Combine a base directory and a child path without probing the filesystem.
pub fn join_path(base : String, child : String) -> String {
  if path_is_absolute(child) {
    normalize_path(child)
  } else if base == "." || base == "" {
    normalize_path(child)
  } else if base == "/" {
    normalize_path("/\{child}")
  } else {
    normalize_path("\{base}/\{child}")
  }
}

///|
/// Resolve an Include argument against its including file and explicitly
/// injected home directory. It never reads process environment variables.
pub fn resolve_include_path(
  including_path : String,
  include_path : String,
  home : String?,
) -> Result[String, String] {
  if path_has_home_prefix(include_path) {
    match home {
      Some(directory) =>
        if include_path == "~" {
          Ok(normalize_path(directory))
        } else {
          Ok(join_path(directory, include_path[2:].to_owned()))
        }
      None =>
        Err("Include path starts with `~/`, but no home directory was supplied")
    }
  } else if path_is_absolute(include_path) {
    Ok(normalize_path(include_path))
  } else {
    Ok(join_path(parent_directory(including_path), include_path))
  }
}