let readdir = dir => {
  let items = ref([]);
  let%lwt dir = Lwt_unix.opendir(dir);
  let iterate = () => {
    let%lwt _ =
      while%lwt (true) {
        let%lwt value = Lwt_unix.readdir(dir);
        if (value.[0] != '.') {
          items := [value, ...items^];
        };
        Lwt.return();
      };

    Lwt.return([]);
  };

  let%lwt items =
    try%lwt(iterate()) {
    | End_of_file => Lwt.return(items^)
    };

  let%lwt _ = Lwt_unix.closedir(dir);
  Lwt.return(items);
};

let writeFile = (path, contents) => {
  let%lwt targetFile = Lwt_io.open_file(~mode=Lwt_io.Output, path);

  let%lwt () = Lwt_io.write(targetFile, contents);
  let%lwt () = Lwt_io.close(targetFile);

  Lwt.return();
};

let exists = path => {
  try%lwt(Lwt_unix.file_exists(path)) {
  | Unix.Unix_error(_, _, _) => Lwt.return(false)
  };
};

let readlink = path =>
  try%lwt(Lwt_unix.readlink(path) |> Lwt.map(x => Ok(x))) {
  | err => Lwt.return_error(err)
  };

[@deriving show]
type file_type =
  | File(string)
  | Dir(string);

// Based on: https://github.com/fastpack/fastpack/blob/9f6aa7d5b83ffef03e73a15679200576ff9dbcb7/FastpackUtil/FS.re#L94
let rec listDirRecursively = dir => {
  switch%lwt (Lwt_unix.lstat(dir)) {
  | {st_kind: Lwt_unix.S_DIR, _} =>
    Lwt_unix.files_of_directory(dir)
    |> Lwt_stream.map_list_s(
         fun
         | "."
         | ".." => Lwt.return([])
         | filename => Filename.concat(dir, filename) |> listDirRecursively,
       )
    |> Lwt_stream.to_list
    |> Lwt.map(xs => List.append(xs, [Dir(dir)]))
  | _ => Lwt.return([File(dir)])
  | exception (Unix.Unix_error(Unix.ENOENT, _, _)) => Lwt.return([])
  };
};

let rmdir = dir => {
  let%lwt entities = listDirRecursively(dir);

  entities
  |> Lwt_list.map_s(
       fun
       | Dir(dir) => Lwt_unix.rmdir(dir)
       | File(file) => Lwt_unix.unlink(file),
     )
  |> Lwt.map(_ => ());
};

type path =
  | Exists(string)
  | Missing(string);

let rec realpath = path => {
  switch%lwt (readlink(path)) {
  | Ok(path) => realpath(path)
  | Error(_) =>
    switch%lwt (exists(path)) {
    | true => Exists(path) |> Lwt.return
    | false => Missing(path) |> Lwt.return
    }
  };
};