#include "utils.hpp"
#include <iostream>
#include <cmath>
#include <cstddef>
#include <random>
#include <glaze/json/generic.hpp>
#include <cstdlib>
#include <filesystem>
#include <qmimedatabase.h>
#include <qmimetype.h>
#include <QColor>
#include <QCoreApplication>
#include <QString>
#include <QRegularExpression>
#include <qrgb.h>

namespace fs = std::filesystem;

std::filesystem::path homeDir() {
  const char *env = getenv("HOME");

  if (!env) return {};

  return env;
}

fs::path compressPath(const fs::path &path) {
  auto homeStr = homeDir().string();
  auto str = path.string();

  if (str.starts_with(homeStr)) { return "~" + str.substr(homeStr.size()); }

  return path;
}

std::filesystem::path expandPath(const std::filesystem::path &path) {
  auto homeStr = homeDir().string();
  auto str = path.string();

  if (str == "~") return homeStr;
  if (str.starts_with("~/")) { return homeStr + str.substr(1); }

  return path;
}

QString getRelativeTimeString(const QDateTime &pastTime) {
  QDateTime const now = QDateTime::currentDateTime();
  qint64 const secondsDiff = pastTime.secsTo(now);

  if (secondsDiff < 0) { return QObject::tr("in the future"); }

  qint64 const days = secondsDiff / (static_cast<qint64>(24 * 3600));
  qint64 const hours = secondsDiff / 3600;
  qint64 const minutes = secondsDiff / 60;

  if (days >= 365) {
    int const years = days / 365;
    return QObject::tr("%n year(s) ago", nullptr, years);
  } else if (days >= 30) {
    int const months = days / 30;
    return QObject::tr("%n month(s) ago", nullptr, months);
  } else if (days >= 1) {
    return QObject::tr("%n day(s) ago", nullptr, static_cast<int>(days));
  } else if (hours >= 1) {
    return QObject::tr("%n hour(s) ago", nullptr, static_cast<int>(hours));
  } else if (minutes >= 1) {
    return QObject::tr("%n minute(s) ago", nullptr, static_cast<int>(minutes));
  } else {
    return QObject::tr("just now");
  }
}

fs::path stripPathComponents(const fs::path &path, int n) {
  fs::path stripedFilePath;
  auto it = path.begin();

  for (int i = 0; i != n && it != path.end(); ++i)
    ++it;

  while (it != path.end()) {
    stripedFilePath /= *it;
    ++it;
  }

  return stripedFilePath;
}

QString qStringFromStdView(std::string_view view) { return QString::fromUtf8(view.data(), view.size()); }

bool isHiddenPath(const std::filesystem::path &path) {
  return std::ranges::any_of(path, [](auto &&path) { return path.string().starts_with('.'); });
}

bool isInHomeDirectory(const std::filesystem::path &path) {
  return path.string().starts_with(homeDir().string());
}

std::vector<fs::path> homeRootDirectories() {
  std::vector<fs::path> paths;
  std::error_code ec;

  for (const auto &entry : fs::directory_iterator(homeDir(), ec)) {
    if (entry.is_directory(ec) && !isHiddenPath(entry.path())) paths.emplace_back(entry.path());
  }

  return paths;
}

std::filesystem::path downloadsFolder() { return homeDir() / "Downloads"; }
std::filesystem::path documentsFolder() { return homeDir() / "Documents"; }

std::string getLastPathComponent(const std::filesystem::path &path) {
  if (!path.has_filename()) {
    if (path.has_parent_path()) return path.parent_path().filename().string();
    return path.string();
  }

  return path.filename().string();
}

QString slugify(const QString &input, const QString &separator) {
  if (input.isEmpty()) { return QString(); }

  QString result = input;

  result = result.toLower();
  result = result.normalized(QString::NormalizationForm_D);
  result.replace(QRegularExpression("[\\s_]+"), separator);

  QString const pattern = QString("[^a-z0-9%1]+").arg(QRegularExpression::escape(separator));
  result.remove(QRegularExpression(pattern));

  QString const escapedSep = QRegularExpression::escape(separator);
  result.remove(QRegularExpression(QString("^%1+|%1+$").arg(escapedSep)));
  result.replace(QRegularExpression(QString("%1{2,}").arg(escapedSep)), separator);

  return result;
}

std::string generatePrefixedId(std::string_view prefix, int length) {
  static std::mt19937 rng(std::random_device{}());
  static constexpr char hex[] = "0123456789abcdef";

  std::string id;
  id.reserve(prefix.size() + 1 + length);
  id.append(prefix);
  id.push_back('-');

  for (int i = 0; i < length; ++i) {
    id.push_back(hex[rng() % 16]);
  }

  return id;
}

QString formatSize(size_t bytes) {
  if (bytes <= 0) { return QCoreApplication::translate("utils", "0 bytes"); }

  const std::vector<QString> units = {QCoreApplication::translate("utils", "bytes"),
                                      QStringLiteral("KB"),
                                      QStringLiteral("MB"),
                                      QStringLiteral("GB"),
                                      QStringLiteral("TB"),
                                      QStringLiteral("PB")};
  const double base = 1024.0;

  int unitIndex = static_cast<int>(std::floor(std::log(bytes) / std::log(base)));

  // Clamp to available units
  unitIndex = std::min(unitIndex, static_cast<int>(units.size() - 1));

  double const size = bytes / std::pow(base, unitIndex);

  // Format with appropriate precision
  QString formattedSize;
  if (unitIndex == 0) {
    // Bytes - no decimal places
    formattedSize = QString::number(static_cast<qint64>(size));
  } else if (size >= 100) {
    // >= 100 - no decimal places
    formattedSize = QString::number(size, 'f', 0);
  } else if (size >= 10) {
    // >= 10 - one decimal place
    formattedSize = QString::number(size, 'f', 1);
  } else {
    // < 10 - two decimal places
    formattedSize = QString::number(size, 'f', 2);
  }

  return formattedSize + " " + units[unitIndex];
}

QString formatCount(int count) {
  if (count > 1'000'000) { return QString("%1M").arg(std::ceil((count / 1'000'000.f) * 10) / 10.f); }
  if (count > 1000) { return QString("%1K").arg(std::ceil((count / 1000.f) * 10) / 10.f); }

  return QString::number(count);
}

namespace Utils {

/**
 * Strips the encoding part in a mime name, typically
 * in 'text/plain;charset-utf8
 */
QString normalizeMimeName(const QString &name) { return name.split(';').at(0); }

bool isX11TextTarget(const QString &text) {
  static const std::set<QString> types = {"UTF8_STRING", "STRING", "TEXT", "COMPOUND_TEXT"};
  return types.contains(text);
}

bool isTextMimeType(const QString &mimeName) {
  if (isX11TextTarget(mimeName)) return true;

  QMimeDatabase const db;
  return isTextMimeType(db.mimeTypeForName(normalizeMimeName(mimeName)));
}

QString rgbaFromColor(const QColor &color) {
  QColor const rgb = color.toRgb();
  return QString("rgba(%1, %2, %3, %4)").arg(rgb.red()).arg(rgb.green()).arg(rgb.blue()).arg(rgb.alphaF());
}

QColor colorFromString(const QString &str) {
  QString const trimmed = str.trimmed();

  if (QColor color(trimmed); color.isValid()) return color;
  if (QColor color("#" + trimmed); color.isValid()) return color;

  QRegularExpression const rgbRegex(R"(rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\))",
                                    QRegularExpression::CaseInsensitiveOption);
  QRegularExpressionMatch const rgbMatch = rgbRegex.match(trimmed);

  if (rgbMatch.hasMatch()) {
    int const r = rgbMatch.captured(1).toInt();
    int const g = rgbMatch.captured(2).toInt();
    int const b = rgbMatch.captured(3).toInt();
    int a = 255;

    if (!rgbMatch.captured(4).isEmpty()) {
      double const alphaFloat = rgbMatch.captured(4).toDouble();
      a = qBound(0, static_cast<int>(alphaFloat * 255), 255);
    }

    return QColor(r, g, b, a);
  }

  // Handle rgb/rgba percentage format: rgb(100%, 0%, 0%, 1.0)
  QRegularExpression const rgbPercentRegex(
      R"(rgba?\s*\(\s*([\d.]+)%\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*([\d.]+)\s*)?\))",
      QRegularExpression::CaseInsensitiveOption);
  QRegularExpressionMatch const rgbPercentMatch = rgbPercentRegex.match(trimmed);

  if (rgbPercentMatch.hasMatch()) {
    int const r = qBound(0, static_cast<int>(rgbPercentMatch.captured(1).toDouble() * 2.55), 255);
    int const g = qBound(0, static_cast<int>(rgbPercentMatch.captured(2).toDouble() * 2.55), 255);
    int const b = qBound(0, static_cast<int>(rgbPercentMatch.captured(3).toDouble() * 2.55), 255);
    int a = 255;

    if (!rgbPercentMatch.captured(4).isEmpty()) {
      double const alphaFloat = rgbPercentMatch.captured(4).toDouble();
      a = qBound(0, static_cast<int>(alphaFloat * 255), 255);
    }

    return QColor(r, g, b, a);
  }

  // Handle hsl/hsla format: hsl(200, 20%, 33%) or hsla(200, 20%, 33%, 0.2)
  QRegularExpression const hslRegex(
      R"(hsla?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*([\d.]+)\s*)?\))",
      QRegularExpression::CaseInsensitiveOption);
  QRegularExpressionMatch const hslMatch = hslRegex.match(trimmed);

  if (hslMatch.hasMatch()) {
    int const h = hslMatch.captured(1).toInt() % 360;
    int const s = qBound(0, hslMatch.captured(2).toInt(), 100) * 255 / 100;
    int const l = qBound(0, hslMatch.captured(3).toInt(), 100) * 255 / 100;
    int a = 255;

    if (!hslMatch.captured(4).isEmpty()) {
      double const alphaFloat = hslMatch.captured(4).toDouble();
      a = qBound(0, static_cast<int>(alphaFloat * 255), 255);
    }

    return QColor::fromHsl(h, s, l, a);
  }

  return Qt::transparent;
}

bool isTextMimeType(const QMimeType &mime) { return mime.inherits("text/plain"); }

std::string slurp(std::istream &ifs) {
  std::array<char, 1 << 16> buf;
  std::string data;

  while (ifs) {
    ifs.read(buf.data(), buf.size());
    data += std::string{buf.data(), buf.data() + ifs.gcount()};
  }

  return data;
}
}; // namespace Utils
