#pragma once

#include "modloader.h"

#include <QDateTime>
#include <QHash>
#include <QList>
#include <QObject>
#include <QSet>
#include <QString>

#include <functional>

class QNetworkAccessManager;

// Списки версий модлоадеров: качает, кэширует в папке лаунчера и отдаёт из
// кэша, пока тот не устарел. Устроен как VersionManifestService и работает в
// GUI-потоке.
//
// Совместимость с версией игры не проверяется отдельной логикой, а заложена в
// структуру данных: Fabric и Quilt отдают список сразу под нужную версию игры,
// а maven-metadata Forge и NeoForge раскладывается по версиям игры при разборе.
// Версии игры, под которую сборок нет, соответствует пустой список — выбрать
// несовместимый лоадер физически нечем.
class ModLoaderVersionService : public QObject
{
    Q_OBJECT

public:
    // ok = true и непустой warning — данные отдали из устаревшего кэша.
    using Callback = std::function<void(bool ok, const QString &warning)>;

    explicit ModLoaderVersionService(QObject *parent = nullptr);

    // Единственная точка входа. Свежий кэш — колбэк отложенно, иначе один
    // сетевой запрос на всех, кто успел попросить.
    void ensureLoaded(ModLoader loader,
                      const QString &gameVersion,
                      Callback callback,
                      bool forceRefresh = false);

    // Новые сборки сверху: первая строка — самая свежая для этой версии игры.
    QList<LoaderVersionEntry> versions(ModLoader loader, const QString &gameVersion) const;
    bool isRefreshing(ModLoader loader, const QString &gameVersion) const;

    // Запись по версии лоадера — из неё установщик берёт ссылку на installer.jar.
    std::optional<LoaderVersionEntry> find(ModLoader loader,
                                           const QString &gameVersion,
                                           const QString &loaderVersion) const;

signals:
    void versionsChanged(const QString &loaderKey, const QString &gameVersion);
    void refreshingChanged();

private:
    // Fabric и Quilt спрашиваются по каждой версии игры отдельно, Forge и
    // NeoForge — одним maven-metadata на все версии сразу. Отсюда два вида
    // ключа свежести: у первых — на версию игры, у вторых — на весь лоадер.
    static bool isPerGameVersion(ModLoader loader);

    struct Bucket
    {
        QHash<QString, QList<LoaderVersionEntry>> byGame;
        QHash<QString, QDateTime> fetchedAtByGame;   // Fabric, Quilt
        QDateTime fetchedAt;                         // Forge, NeoForge
        bool cacheRead = false;
    };

    Bucket &bucket(ModLoader loader);
    const Bucket &bucket(ModLoader loader) const;

    static QString requestKey(ModLoader loader, const QString &gameVersion);
    bool isFresh(ModLoader loader, const QString &gameVersion) const;

    bool readCache(ModLoader loader);
    void writeCache(ModLoader loader);
    void startRefresh(ModLoader loader, const QString &gameVersion);
    void finishRefresh(ModLoader loader,
                       const QString &gameVersion,
                       bool ok,
                       const QString &warning);

    // Разбор ответов. Возвращают пустое при испорченном ответе — хороший кэш им
    // не затирается.
    static QList<LoaderVersionEntry> parseFabricLike(const QByteArray &payload,
                                                     ModLoader loader,
                                                     const QString &gameVersion);
    static QHash<QString, QList<LoaderVersionEntry>> parseMavenMetadata(const QByteArray &xml,
                                                                       ModLoader loader,
                                                                       bool legacyNeoForge);

    QNetworkAccessManager *m_network;
    QHash<int, Bucket> m_buckets;
    QHash<QString, QList<Callback>> m_pending;
    QSet<QString> m_inFlight;
};
