///|
fn path_separator() -> String {
  if @platform.is_windows() {
    ";"
  } else {
    ":"
  }
}

///|
fn has_separator(name : String) -> Bool {
  name.contains("/") ||
  (@platform.is_windows() && (name.contains("\\") || name.contains(":")))
}

///|
async fn executable(path : String) -> Bool {
  (@fs.kind(path) catch { _ => Unknown }) == Regular &&
  (@fs.can_execute(path) catch { _ => false })
}

///|
fn executable_names(name : String) -> Array[String] {
  if !@platform.is_windows() {
    return [name]
  }
  let extensions = @env.get_env_var("PATHEXT")
    .unwrap_or(".COM;.EXE;.BAT;.CMD")
    .split(";")
    .filter(value => value.has_prefix(".") && value.length() > 1)
    .map(value => value.to_owned())
    .collect()
  if extensions.any(extension => {
      name.to_lower().has_suffix(extension.to_lower())
    }) {
    [name]
  } else {
    extensions.map(extension => name + extension)
  }
}

///|
async fn candidates(name : String) -> Array[String] {
  if has_separator(name) {
    let found : Array[String] = []
    for candidate in executable_names(name) {
      if executable(candidate) {
        found.push(candidate)
      }
    }
    return found
  }
  let path = @env.get_env_var("PATH").unwrap_or("")
  let result : Array[String] = []
  for directory in path.split(path_separator()) {
    let root = directory.to_owned()
    let base = if root == "" { "." } else { root }
    for filename in executable_names(name) {
      let candidate = if base == "." {
        "." + @path.sep.to_string() + filename
      } else {
        @path.Path(base).join(Path(filename)).to_string()
      }
      if executable(candidate) {
        result.push(candidate)
      }
    }
  }
  result
}

///|
async fn main {
  let parsed = @cli.parse(@env.args()[1:], [
    @cli.flag("all", short='a'),
    @cli.flag("silent", short='s'),
    @cli.flag("help"),
    @cli.flag("version"),
  ]) catch {
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("which: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(
      "Usage: which [-as] NAME...\nFind executable regular files in PATH. Windows uses PATHEXT.\n  -a, --all     print all matches\n  -s, --silent  return status without output\n      --help    display this help and exit\n      --version display version and exit\n",
    )
    return
  }
  if parsed.contains("version") {
    @stdio.stdout.write("which (cli) \{command_version}\n")
    return
  }
  if parsed.operands.is_empty() {
    @stdio.stderr.write("which: missing command name\n")
    @sys.exit(2)
    return
  }
  let mut missing = false
  for name in parsed.operands {
    let matches = candidates(name)
    if matches.is_empty() {
      missing = true
      if !parsed.contains("silent") {
        @stdio.stderr.write("\{name} not found\n")
      }
      continue
    }
    if !parsed.contains("silent") {
      if parsed.contains("all") {
        for match_path in matches {
          @stdio.stdout.write(match_path + "\n")
        }
      } else {
        @stdio.stdout.write(matches[0] + "\n")
      }
    }
  }
  if missing {
    @sys.exit(1)
  }
}