#include "torlinkc/util/open_folder.hpp"

#include <chrono>
#include <filesystem>
#include <thread>

#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

namespace torlinkc {

namespace {

// Forks, execs the first candidate that exists is left to the shell's PATH
// lookup (execvp), and waits up to ~4s for a clean exit. Never throws.
bool spawnAndWait(const std::vector<std::vector<std::string>>& candidates) {
  for (const auto& argvStrings : candidates) {
    pid_t pid = fork();
    if (pid < 0) continue;
    if (pid == 0) {
      std::vector<char*> argv;
      for (const auto& s : argvStrings) argv.push_back(const_cast<char*>(s.c_str()));
      argv.push_back(nullptr);
      execvp(argv[0], argv.data());
      _exit(127);  // execvp only returns on failure
    }

    bool exited = false;
    int status = 0;
    for (int i = 0; i < 40; ++i) {  // ~4s in 100ms slices
      if (waitpid(pid, &status, WNOHANG) == pid) {
        exited = true;
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    if (!exited) {
      kill(pid, SIGKILL);
      waitpid(pid, nullptr, 0);
      continue;
    }
    if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return true;
  }
  return false;
}

}  // namespace

bool openFolder(const std::string& dir) {
  // Check the path ourselves first, matching the original's rationale (some
  // launchers silently open a fallback location for a nonexistent path,
  // which would look like success).
  std::error_code ec;
  if (dir.empty() || !std::filesystem::exists(dir, ec)) return false;

#ifdef __APPLE__
  return spawnAndWait({{"open", dir}});
#else
  return spawnAndWait({{"xdg-open", dir}, {"gio", "open", dir}});
#endif
}

}  // namespace torlinkc
