#pragma once

#include "minecraftversion.h"

#include <QCryptographicHash>
#include <QJsonObject>
#include <QList>
#include <QObject>
#include <QPair>
#include <QQueue>
#include <QString>
#include <QStringList>
#include <QUrl>

#include <memory>

class QNetworkAccessManager;
class QNetworkReply;
class QSaveFile;
class QTimer;
class VersionManifestService;

// Один файл, который нужно положить в .minecraft.
struct DownloadTask
{
    enum Kind { ClientJar, Library, AssetIndex, AssetObject, LoggingConfig };

    Kind kind = ClientJar;
    QUrl url;
    QString path;      // абсолютный путь назначения
    QString sha1;      // пусто — не проверяем
    qint64 size = 0;   // 0 — размер неизвестен
    QString label;     // что показать в панели прогресса
    int attempts = 0;
};

// Файл в процессе скачивания. Пишем потоком: держать десятки мегабайт в памяти
// незачем, а нескольких параллельных загрузок хватило бы на сотни.
struct ActiveDownload
{
    DownloadTask task;
    QNetworkReply *reply = nullptr;
    std::unique_ptr<QSaveFile> file;
    QCryptographicHash hash{QCryptographicHash::Sha1};
    qint64 received = 0;
};

// Фоновая установка версии в .minecraft: описание версии, клиентский jar,
// библиотеки, индекс ресурсов и сами ресурсы. Одна версия за раз, остальные
// ждут в очереди. Всё в GUI-потоке, как и остальной сетевой код лаунчера.
class VersionInstaller : public QObject
{
    Q_OBJECT

public:
    explicit VersionInstaller(VersionManifestService *manifest, QObject *parent = nullptr);
    ~VersionInstaller() override;

    bool isRunning() const { return m_running; }
    QString versionId() const { return m_versionId; }
    QString stage() const { return m_stage; }
    QString currentFile() const { return m_currentLabel; }

    qint64 bytesDone() const;
    qint64 bytesTotal() const { return m_bytesTotal; }
    double fraction() const;   // 0..1, либо -1 пока итог неизвестен

    void install(const QString &gameDir, const QString &versionId);
    bool isQueued(const QString &versionId) const;
    void cancel();

signals:
    void started(const QString &versionId);
    void progressChanged();   // не чаще десяти раз в секунду
    void finished(const QString &versionId);
    void failed(const QString &versionId, const QString &message);
    void canceled(const QString &versionId);

private:
    void startNext();
    void resolveManifestEntry();
    void fetchVersionJson(const QUrl &url, const QString &expectedSha1);
    // Общий хвост: описание версии пришло из сети или уже лежало на диске.
    void adoptVersionJson(const QByteArray &payload, bool alreadySaved);
    bool prepareTasks();
    void enqueue(DownloadTask task);

    void schedulePump();
    void pump();
    void startTask(DownloadTask task);
    void handleReplyFinished(const std::shared_ptr<ActiveDownload> &context);
    bool retryOrFail(DownloadTask task, const QString &reason);
    void expandAssetIndex(const QString &indexPath);

    void afterDownloads();
    void startLegacyCopy();
    void legacyTick();
    void complete();

    void setStage(const QString &stage);
    void scheduleProgress();
    void flushProgress();

    void resetInstallState();
    void failInstall(const QString &message);
    void abandonCurrent();     // версия уступает место родителю, без сигналов
    void finalizeCancel();

    VersionManifestService *m_manifest;
    QNetworkAccessManager *m_network;

    QQueue<QPair<QString, QString>> m_pendingInstalls;   // {gameDir, versionId}
    QString m_gameDir;
    QString m_versionId;
    QJsonObject m_versionJson;
    MinecraftVersion m_version;

    QQueue<DownloadTask> m_queue;
    QList<std::shared_ptr<ActiveDownload>> m_active;
    QStringList m_warnings;

    qint64 m_bytesSettled = 0;
    qint64 m_bytesTotal = 0;
    qint64 m_assetsDeclaredTotal = 0;

    bool m_running = false;
    bool m_canceling = false;
    bool m_pumpScheduled = false;
    // Существующие файлы сверяются только по размеру: перехеширование сотен
    // мегабайт при каждом добавлении версии дороже, чем риск битого файла.
    // Флаг оставлен под будущую кнопку «проверить файлы».
    bool m_verifyExisting = false;

    QString m_stage;
    QString m_currentLabel;

    QTimer *m_progressTimer;
    bool m_progressDirty = false;

    // Ресурсы до 1.6 игра ищет не в assets/objects, а в плоской папке.
    QJsonObject m_assetObjects;
    bool m_assetsVirtual = false;
    bool m_assetsMapToResources = false;
    QList<QPair<QString, QString>> m_legacyCopies;   // {источник, назначение}
    int m_legacyIndex = 0;
    QTimer *m_legacyTimer;

    int m_inheritDepth = 0;
};
