///|
/// Normalize a build path without consulting the host filesystem.
/// Both slash styles are accepted so plans are reproducible across CI hosts.
pub fn normalize_build_path(path : String) -> Result[String, String] {
  if path == "" {
    return Err("build path is empty")
  }
  let normalized_separators = path.replace(old="\\", new="/")
  let absolute = normalized_separators[0] == '/' ||
    (normalized_separators.length() > 1 && normalized_separators[1] == ':')
  if absolute {
    return Err("absolute paths are not portable build inputs")
  }
  let segments : Array[String] = []
  let mut current = ""
  let mut escaped_workspace = false
  fn flush_segment() -> Unit {
    if current == "" || current == "." {
      current = ""
    } else if current == ".." {
      if segments.is_empty() {
        escaped_workspace = true
        current = ""
      } else {
        ignore(segments.pop())
        current = ""
      }
    } else {
      segments.push(current)
      current = ""
    }
  }
  for ch in normalized_separators {
    if ch == '/' {
      flush_segment()
    } else {
      current += "\{ch}"
    }
  }
  flush_segment()
  if escaped_workspace || current == ".." {
    return Err("build path escapes the workspace")
  }
  if segments.is_empty() {
    Err("build path resolves to the workspace root")
  } else {
    Ok(join_strings(segments, "/"))
  }
}

///|
pub fn join_build_path(parts : Array[String]) -> Result[String, String] {
  normalize_build_path(join_strings(parts, "/"))
}

///|
pub fn is_relative_build_path(path : String) -> Bool {
  match normalize_build_path(path) {
    Ok(_) => true
    Err(_) => false
  }
}

///|
pub fn build_path_extension(path : String) -> String {
  let normalized = path.replace(old="\\", new="/")
  let slash = match normalized.rev_find("/") {
    Some(index) => index + 1
    None => 0
  }
  let dot = match normalized.rev_find(".") {
    Some(index) => index
    None => -1
  }
  if dot < slash || dot < 0 {
    ""
  } else {
    normalized[dot:].to_owned()
  }
}

///|
pub fn build_path_stem(path : String) -> String {
  let normalized = path.replace(old="\\", new="/")
  let slash = match normalized.rev_find("/") {
    Some(index) => index + 1
    None => 0
  }
  let dot = match normalized.rev_find(".") {
    Some(index) => index
    None => normalized.length()
  }
  let end = if dot < slash { normalized.length() } else { dot }
  normalized[slash:end].to_owned()
}

///|
pub fn build_path_directory(path : String) -> String {
  let normalized = path.replace(old="\\", new="/")
  match normalized.rev_find("/") {
    Some(index) => normalized[:index].to_owned()
    None => ""
  }
}