open Fnm;

let symlinkExists = path =>
  try%lwt (Lwt_unix.lstat(path) |> Lwt.map(_ => true)) {
  | _ => Lwt.return(false)
  };

let rec makeTemporarySymlink = () => {
  let suggestedName =
    Filename.concat(
      Filename.get_temp_dir_name(),
      "fnm-shell-"
      ++ (Random.int32(9999999 |> Int32.of_int) |> Int32.to_string),
    );

  let%lwt exists = symlinkExists(suggestedName);

  if (exists) {
    let%lwt suggestedName = makeTemporarySymlink();
    Lwt.return(suggestedName);
  } else {
    let%lwt _ = Lwt_unix.symlink(Directories.defaultVersion, suggestedName);
    Lwt.return(suggestedName);
  };
};

let printUseOnCd = (~shell) =>
  switch (shell) {
  | System.Shell.Bash => {|
    __fnmcd () {
      cd $@

      if [[ -f .node-version && .node-version ]]; then
        echo "fnm: Found .node-version"
        fnm use
      elif [[ -f .nvmrc && .nvmrc ]]; then
        echo "fnm: Found .nvmrc"
        fnm use
      fi
    }

    alias cd=__fnmcd
  |}
  | Fish => {|
      function _fnm_autoload_hook --on-variable PWD --description 'Change Node version on directory change'
        status --is-command-substitution; and return
        if test -f .node-version
          echo "fnm: Found .node-version"
          fnm use
        else if test -f .nvmrc
          echo "fnm: Found .nvmrc"
          fnm use
        end
      end
    |}
  | Zsh => {|
      autoload -U add-zsh-hook
      _fnm_autoload_hook () {
        if [[ -f .node-version && -r .node-version ]]; then
          echo "fnm: Found .node-version"
          fnm use
        elif [[ -f .nvmrc && -r .nvmrc ]]; then
          echo "fnm: Found .nvmrc"
          fnm use
        fi
      }

      add-zsh-hook chpwd _fnm_autoload_hook \
        && _fnm_autoload_hook
    |}
  };

let run =
    (~forceShell, ~multishell, ~nodeDistMirror, ~fnmDir, ~useOnCd, ~logLevel) => {
  open System.Shell;

  let%lwt shell =
    switch (forceShell) {
    | None =>
      switch%lwt (System.Shell.infer()) {
      | None => Lwt.return(Bash)
      | Some(shell) => Lwt.return(shell)
      }
    | Some(shell) => Lwt.return(shell)
    };

  Random.self_init();

  let%lwt path =
    multishell
      ? makeTemporarySymlink() : Lwt.return(Directories.globalCurrentVersion);

  switch (shell) {
  | Bash
  | Zsh =>
    Printf.sprintf("export PATH=%s/bin:$PATH", path) |> Console.log;
    Printf.sprintf("export %s=%s", Config.FNM_MULTISHELL_PATH.name, path)
    |> Console.log;
    Printf.sprintf("export %s=%s", Config.FNM_DIR.name, fnmDir) |> Console.log;
    Printf.sprintf(
      "export %s=%s",
      Config.FNM_NODE_DIST_MIRROR.name,
      nodeDistMirror,
    )
    |> Console.log;
    Printf.sprintf(
      "export %s=%s",
      Config.FNM_LOGLEVEL.name,
      Fnm.LogLevel.toString(logLevel),
    )
    |> Console.log;
  | Fish =>
    Printf.sprintf("set -gx PATH %s/bin $PATH;", path) |> Console.log;
    Printf.sprintf("set -gx %s %s;", Config.FNM_MULTISHELL_PATH.name, path)
    |> Console.log;
    Printf.sprintf("set -gx %s %s;", Config.FNM_DIR.name, fnmDir)
    |> Console.log;
    Printf.sprintf(
      "set -gx %s %s",
      Config.FNM_NODE_DIST_MIRROR.name,
      nodeDistMirror,
    )
    |> Console.log;
  };

  if (useOnCd) {
    printUseOnCd(~shell) |> Console.log;
  };

  Lwt.return();
};