#include "vicinae.hpp"
#include "common/common.hpp"
#include "utils.hpp"
#include <qcoreapplication.h>
#include <qlogging.h>
#include <qprocess.h>
#include <QProcessEnvironment>
#include <ranges>
#include <set>

#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
#include "xdgpp/env/env.hpp"
#endif

namespace fs = std::filesystem;

#ifdef Q_OS_WIN
static fs::path winLocalAppData() {
  const QString local = qEnvironmentVariable("LOCALAPPDATA");
  if (!local.isEmpty()) return fs::path(local.toStdWString()) / "vicinae";
  return fs::temp_directory_path() / "vicinae";
}
#endif

fs::path Omnicast::runtimeDir() { return vicinae::runtimeDir(); }

fs::path Omnicast::dataDir() {
#ifdef Q_OS_MACOS
  return homeDir() / ".local" / "share" / "vicinae";
#elif defined(Q_OS_WIN)
  return winLocalAppData() / "data";
#else
  return xdgpp::dataHome() / "vicinae";
#endif
}

fs::path Omnicast::configDir() {
#ifdef Q_OS_MACOS
  return homeDir() / ".config" / "vicinae";
#elif defined(Q_OS_WIN)
  return winLocalAppData() / "config";
#else
  return xdgpp::configHome() / "vicinae";
#endif
}

fs::path Omnicast::stateDir() { return vicinae::stateDir(); }

fs::path Omnicast::cacheDir() {
#ifdef Q_OS_MACOS
  return homeDir() / ".cache" / "vicinae";
#elif defined(Q_OS_WIN)
  return winLocalAppData() / "cache";
#else
  return xdgpp::cacheHome() / "vicinae";
#endif
}

fs::path Omnicast::dataHome() {
#ifdef Q_OS_MACOS
  return homeDir() / ".local" / "share";
#elif defined(Q_OS_WIN)
  return winLocalAppData().parent_path();
#else
  return xdgpp::dataHome();
#endif
}

fs::path Omnicast::bundleResourceDir() {
#ifdef Q_OS_MACOS
  // Inside Vicinae.app/Contents/MacOS/<exe>, Resources is one level up.
  auto const appDir = QCoreApplication::applicationDirPath().toStdString();
  return fs::path(appDir).parent_path() / "Resources";
#else
  return {};
#endif
}

std::vector<fs::path> Omnicast::systemDataDirs() {
  std::vector<fs::path> paths;
#ifdef Q_OS_MACOS
  if (auto bundle = bundleResourceDir(); !bundle.empty()) { paths.emplace_back(std::move(bundle)); }
#elif defined(Q_OS_WIN)
  auto const appDir = QCoreApplication::applicationDirPath().toStdString();
  paths.emplace_back((fs::path(appDir) / VICINAE_DATA_ROOT_REL).lexically_normal());
#else
  auto const dd = xdgpp::dataDirs();
  paths.reserve(dd.size());
  for (auto const &dir : dd) {
    paths.emplace_back(dir / "vicinae");
  }
#endif
  return paths;
}

std::vector<fs::path> Omnicast::dataSearchPaths(std::string_view subdir) {
  std::vector<fs::path> paths;
  auto const sub = fs::path(subdir);
  auto const user = dataDir() / sub;

  paths.reserve(1 + systemDataDirs().size());
  paths.emplace_back(user);
  for (auto const &dir : systemDataDirs()) {
    auto p = dir / sub;
    if (p != user) paths.emplace_back(std::move(p));
  }
  return paths;
}

std::string Omnicast::commandSocketName() { return vicinae::serverSocketName(); }
fs::path Omnicast::pidFile() { return runtimeDir() / "vicinae.pid"; }

void Omnicast::ensureDirectories() {
  for (auto const &dir : {runtimeDir(), dataDir(), stateDir(), configDir(), cacheDir()}) {
    std::error_code ec;
    fs::create_directories(dir, ec);
    if (ec) { qWarning() << "Failed to create directory" << dir.c_str() << ec.message(); }
  }
}

std::vector<fs::path> Omnicast::systemPaths() {
  const char *path = getenv("PATH");

  if (!path) return {};

  std::set<fs::path> seen;
  std::vector<fs::path> paths;

#ifdef Q_OS_WIN
  constexpr std::string_view sep = ";";
#else
  constexpr std::string_view sep = ":";
#endif

  for (const auto &part : std::views::split(std::string_view(path), sep)) {
    fs::path const path = std::string_view(part.begin(), part.end());

    if (seen.contains(path)) continue;

    seen.insert(path);
    paths.emplace_back(path);
  }

  return paths;
}
