///|
priv suberror RealpathError {
  RealpathError(String)
  ReadlinkBoundary(String)
}

///|
priv enum Existence {
  Existing
  AllButLast
  Missing
}

///|
fn slashes(path : String) -> String {
  if @platform.is_windows() {
    path.replace_all(old="\\", new="/")
  } else {
    path
  }
}

///|
fn path_root(path : String) -> String {
  if @platform.is_windows() {
    slashes(@winpath.Path(path).root().to_owned())
  } else {
    "/"
  }
}

///|
fn append_path(parent : String, name : String) -> String {
  parent + (if parent.has_suffix("/") { "" } else { "/" }) + name
}

///|
fn error_text(error : Error) -> String {
  match error {
    RealpathError(message) => message
    @os_error.OSError(_) as error if error.is_ENOENT() =>
      "No such file or directory"
    @os_error.OSError(_) as error if error.is_ENOTDIR() => "Not a directory"
    @os_error.OSError(_) as error if error.is_EACCES() => "Permission denied"
    _ => error.to_string()
  }
}

///|
async fn resolve_path(
  path : String,
  existence : Existence,
  strip : Bool,
) -> String {
  if path.is_empty() {
    raise RealpathError("No such file or directory")
  }
  let cwd = slashes(@fs.realpath("."))
  let input = slashes(path)
  let absolute = if @path.Path(path).is_absolute() {
    if @platform.is_windows() && path_root(input) == "/" {
      path_root(cwd) + input[1:].to_owned()
    } else {
      input
    }
  } else if @platform.is_windows() && input.length() >= 2 && input[1] == ':' {
    let drive_base = slashes(
      @path.Path(input[:2].to_owned()).resolve().to_string(),
    )
    append_path(drive_base, input[2:].to_owned())
  } else {
    append_path(cwd, input)
  }
  let root = path_root(absolute)
  let parts = absolute[root.length():]
    .split("/")
    .filter(p => !p.is_empty())
    .map(p => p.to_owned())
    .collect()
  let mut current = root
  for index, part in parts {
    if part == "." {
      continue
    }
    if part == ".." {
      current = slashes(@path.Path(current).dirname().to_string())
      continue
    }
    let candidate = append_path(current, part)
    if strip && existence is Missing {
      current = candidate
      continue
    }
    let kind = try @fs.kind(candidate, follow_symlink=strip) catch {
      @os_error.OSError(_) as error if error.is_ENOENT() ||
        (existence is Missing && error.is_ENOTDIR()) => None
      error => raise error
    } noraise {
      kind => Some(kind)
    }
    let must_directory = index + 1 < parts.length() || absolute.has_suffix("/")
    match kind {
      None => {
        if existence is Existing || (existence is AllButLast && must_directory) {
          raise RealpathError("No such file or directory")
        }
        current = candidate
      }
      Some(SymLink) if !strip => {
        current = slashes(
          @fs.realpath(candidate) catch {
            error => {
              if existence is Missing ||
                (existence is AllButLast && !must_directory) {
                raise ReadlinkBoundary(candidate)
              }
              raise RealpathError(error_text(error))
            }
          },
        )
        if must_directory &&
          !(existence is Missing) &&
          !(@fs.kind(current) is Directory) {
          raise RealpathError("Not a directory")
        }
      }
      Some(kind) => {
        if must_directory && !(existence is Missing) && !(kind is Directory) {
          raise RealpathError("Not a directory")
        }
        current = if !strip && @platform.is_windows() {
          slashes(@fs.realpath(candidate))
        } else {
          candidate
        }
      }
    }
  }
  @platform.normalize_input_separators(current)
}

///|
fn inside(path : String, base : String) -> Bool {
  let path = slashes(path)
  let base = slashes(base)
  let path = if @platform.is_windows() { path.to_lower() } else { path }
  let base = if @platform.is_windows() { base.to_lower() } else { base }
  path == base ||
  path.has_prefix(base + (if base.has_suffix("/") { "" } else { "/" }))
}

///|
fn relative_path(path : String, base : String) -> String {
  let result = @path.Path(path).relative(base=@path.Path(base)).to_string()
  if result.is_empty() {
    "."
  } else {
    result
  }
}

///|
async fn main {
  let parsed = @cli.parse(@env.args()[1:], [
    @cli.flag("canonicalize-existing", short='e'),
    @cli.flag("canonicalize-missing", short='m'),
    @cli.flag("canonicalize", short='E'),
    @cli.flag("strip", short='s'),
    @cli.flag("no-symlinks"),
    @cli.flag("quiet", short='q'),
    @cli.flag("zero", short='z'),
    @cli.option("relative-to"),
    @cli.option("relative-base"),
    @cli.flag("help"),
    @cli.flag("version"),
  ]) catch {
    @cli.CliError(kind=MissingOptionValue, option~, ..) if option ==
      "relative-to" ||
      option == "relative-base" => {
      @stdio.stderr.write(
        "realpath: option '--\{option}' requires an argument\nTry 'realpath --help' for more information.\n",
      )
      @sys.exit(1)
      return
    }
    @cli.CliError(option~, message~, ..) => {
      @stdio.stderr.write("realpath: \{message}: '\{option}'\n")
      @sys.exit(2)
      return
    }
  }
  if parsed.contains("help") {
    @stdio.stdout.write(
      "Usage: realpath [OPTION]... NAME...\n" +
      "  -e, --canonicalize-existing  require every component to exist\n" +
      "  -E, --canonicalize           allow a missing final component (default)\n" +
      "  -m, --canonicalize-missing   allow missing or non-directory components\n" +
      "  -s, --strip, --no-symlinks   do not expand symbolic links\n" +
      "      --relative-to=DIR        print relative to DIR\n" +
      "      --relative-base=DIR      use relative output only inside DIR\n" +
      "  -q, --quiet                 suppress operand errors\n" +
      "  -z, --zero                  terminate output with NUL\n" +
      "      --help                  display this help and exit\n" +
      "      --version               display version and exit\n",
    )
    return
  }
  if parsed.contains("version") {
    @stdio.stdout.write("realpath \{command_version}\n")
    return
  }
  if parsed.operands.is_empty() {
    @stdio.stderr.write(
      "realpath: missing operand\nTry 'realpath --help' for more information.\n",
    )
    @sys.exit(1)
    return
  }
  let existence = match
    parsed.last_occurrence([
      "canonicalize-existing", "canonicalize-missing", "canonicalize",
    ]) {
    Some("canonicalize-existing") => Existing
    Some("canonicalize-missing") => Missing
    _ => AllButLast
  }
  let strip = parsed.contains("strip") || parsed.contains("no-symlinks")
  let base_arg = parsed.last_value("relative-base")
  let to_arg = match parsed.last_value("relative-to") {
    Some(_) as value => value
    None => base_arg
  }
  let mut relative_to : String? = None
  let mut relative_base : String? = None
  for (argument, is_base) in [(to_arg, false), (base_arg, true)] {
    if argument is Some(argument) {
      let value = resolve_path(argument, existence, strip) catch {
        ReadlinkBoundary(_) => {
          @stdio.stderr.write(
            "realpath: unsupported capability: unresolved symbolic-link target (readlink)\n",
          )
          @sys.exit(2)
          return
        }
        error => {
          @stdio.stderr.write("realpath: \{argument}: \{error_text(error)}\n")
          @sys.exit(1)
          return
        }
      }
      if existence is Existing && !(@fs.kind(value) is Directory) {
        @stdio.stderr.write("realpath: \{argument}: Not a directory\n")
        @sys.exit(1)
        return
      }
      if is_base {
        relative_base = Some(value)
      } else {
        relative_to = Some(value)
      }
    }
  }
  if relative_base is Some(base) && relative_to is Some(to) && !inside(to, base) {
    relative_to = None
  }
  let separator = if parsed.contains("zero") { "\u0000" } else { "\n" }
  let mut status = 0
  for operand in parsed.operands {
    let value = resolve_path(operand, existence, strip) catch {
      ReadlinkBoundary(_) => {
        if !parsed.contains("quiet") {
          @stdio.stderr.write(
            "realpath: unsupported capability: unresolved symbolic-link target (readlink)\n",
          )
        }
        status = 2
        continue
      }
      error => {
        if !parsed.contains("quiet") {
          @stdio.stderr.write("realpath: \{operand}: \{error_text(error)}\n")
        }
        status = status.max(1)
        continue
      }
    }
    let relative = relative_base
      .map(base => inside(value, base))
      .unwrap_or(true)
    let output = if relative && relative_to is Some(to) {
      relative_path(value, to)
    } else {
      value
    }
    @stdio.stdout.write(output + separator)
  }
  if status != 0 {
    @sys.exit(status)
  }
}