#include "raycast-store.hpp"
#include "environment.hpp"
#include "utils/capabilities.hpp"
#include <algorithm>

static bool availableOnCurrentPlatform(const Raycast::Extension &ext) {
  // Raycast won't advertise linux compatibility, but we still want them on Linux (best-effort support)
  if constexpr (!Raycast::isNativePlatform()) return true;

  // no platforms means every platform
  if (!ext.platforms) return true;

  return std::ranges::contains(*ext.platforms, platform::extensionPlatform(),
                               [](const QString &p) { return p.toLower(); });
}

RaycastStoreService::RaycastStoreService() {
  m_client.setBaseUrl(QStringLiteral("https://backend.raycast.com/api/v1"));
  m_vicinaeClient.setBaseUrl(Environment::vicinaeApiBaseUrl());
}

const http::RequestOptions RaycastStoreService::s_requestOpts = {
    .cachePolicy = QNetworkRequest::PreferCache,
    .allowHttp2 = false,
    .contentType = QStringLiteral("application/json"),
};

void RaycastStoreService::postProcess(Raycast::Extension &ext) {
  ext.id = QString("store.raycast.%1").arg(ext.name);
  for (auto &cmd : ext.commands) {
    cmd.extensionIcons = ext.icons;
  }
}

void RaycastStoreService::postProcess(std::vector<Raycast::Extension> &extensions) {
  std::erase_if(extensions, [](const auto &ext) { return !availableOnCurrentPlatform(ext); });
  for (auto &ext : extensions) {
    postProcess(ext);
  }
}

QFuture<Raycast::ExtensionResult> RaycastStoreService::fetchExtension(const QString &author,
                                                                      const QString &name) {
  auto url = QString("/extensions/%1/%2").arg(author, name);

  return m_client.get<Raycast::Extension>(url, s_requestOpts)
      .then([](http::Client::Result<Raycast::Extension> result) -> Raycast::ExtensionResult {
        if (!result) return std::unexpected(result.error());
        postProcess(*result);
        return *std::move(result);
      });
}

QFuture<Raycast::ListResult> RaycastStoreService::search(const QString &query) {
  auto url = QString("/store_listings/search?q=%1").arg(query);

  return m_client.get<Raycast::ListApiResponse>(url, s_requestOpts)
      .then([](http::Client::Result<Raycast::ListApiResponse> result) -> Raycast::ListResult {
        if (!result) return std::unexpected(result.error());
        auto extensions = std::move(result->data);
        postProcess(extensions);
        return Raycast::ListResponse{std::move(extensions)};
      });
}

QFuture<Raycast::DownloadExtensionResult> RaycastStoreService::downloadExtension(const QUrl &url) {
  return m_client.getRaw(url.toString(), s_requestOpts)
      .then([](http::Client::Result<QByteArray> result) -> Raycast::DownloadExtensionResult {
        if (!result) return std::unexpected(result.error());
        return *std::move(result);
      });
}

QFuture<Raycast::CompatResult> RaycastStoreService::fetchCompat() {
  if (!Raycast::hasCompatSheet() || m_compatFetched) {
    return QtFuture::makeReadyValueFuture<Raycast::CompatResult>(m_compat);
  }

  return m_vicinaeClient.get<Raycast::CompatMap>(QStringLiteral("/raycast/get-compat"))
      .then([this](http::Client::Result<Raycast::CompatMap> result) -> Raycast::CompatResult {
        if (!result) {
          qWarning() << "[RaycastStore] compat fetch failed:" << result.error();
          return Raycast::CompatMap{};
        }
        m_compat = std::move(*result);
        m_compatFetched = true;
        return m_compat;
      });
}

QFuture<Raycast::ListResult>
RaycastStoreService::fetchExtensions(const Raycast::ListPaginationOptions &opts) {
  if (auto it = m_cachedPages.find(opts.page); it != m_cachedPages.end()) {
    return QtFuture::makeReadyValueFuture<Raycast::ListResult>(it->second);
  }

  auto url = QString("/store_listings?page=%1&per_page=%2").arg(opts.page).arg(opts.perPage);

  return m_client.get<Raycast::ListApiResponse>(url, s_requestOpts)
      .then([this,
             page = opts.page](http::Client::Result<Raycast::ListApiResponse> result) -> Raycast::ListResult {
        if (!result) return std::unexpected(result.error());
        auto extensions = std::move(result->data);
        postProcess(extensions);
        Raycast::ListResponse response{std::move(extensions)};
        m_cachedPages.insert({page, response});
        return response;
      });
}
