#pragma once

#include "javaruntime.h"

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

#include <memory>

class QJsonObject;
class QNetworkAccessManager;
class QNetworkReply;
class QProcess;
class QSaveFile;
class QTimer;

// Состояние распаковки zip. Спрятано в .cpp, чтобы приватный заголовок
// QZipReader не расходился по проекту вместе с этим.
struct ZipExtraction;

// Один файл рантайма Mojang.
struct JavaFileTask
{
    QUrl url;
    QString path;        // абсолютный путь назначения
    QString sha1;
    qint64 size = 0;
    bool executable = false;
    int attempts = 0;
};

// Файл в процессе скачивания. Пишем потоком: рантайм — это две сотни мегабайт.
struct JavaActiveDownload
{
    JavaFileTask task;
    QNetworkReply *reply = nullptr;
    std::unique_ptr<QSaveFile> file;
    QCryptographicHash hash{QCryptographicHash::Sha1};
    qint64 received = 0;
};

// Установка сборки Java в <root>/java/<id>. Два разных пути под одним фасадом:
//
//  * Temurin отдаёт один архив — качаем, сверяем sha256 и распаковываем;
//
//  * Mojang отдаёт манифест с деревом файлов — качаем файлы по отдельности,
//    как это делает официальный лаунчер.
//
// Набор геттеров повторяет VersionInstaller: панель загрузки в интерфейсе
// читает их одинаково, независимо от того, кто сейчас работает.
class JavaInstaller : public QObject
{
    Q_OBJECT

public:
    explicit JavaInstaller(QObject *parent = nullptr);
    ~JavaInstaller() override;

    bool isRunning() const { return m_running; }
    QString runtimeId() const { return m_entry.id; }
    QString label() const { return m_label; }
    QString stage() const { return m_stage; }
    QString currentFile() const { return m_currentFile; }

    // Байты считаются только на загрузке: на распаковке считать нечего, и панель
    // по нулевому итогу сама прячет мегабайты.
    qint64 bytesDone() const;
    qint64 bytesTotal() const { return m_bytesTotal; }
    double fraction() const;   // 0..1, либо -1 пока итог неизвестен

    void install(const JavaRuntimeEntry &entry);
    void cancel();

signals:
    void started(const QString &label);
    void progressChanged();
    void finished(const QString &runtimeId, const QString &javaPath);
    void failed(const QString &label, const QString &message);
    void canceled(const QString &label);

private:
    // ── Общее ───────────────────────────────────────────────────────────
    void beginArchive();
    void beginMojang();
    void complete();
    void fail(const QString &message);
    void reset();
    void setStage(const QString &stage);
    void scheduleProgress();
    void flushProgress();

    // ── Temurin ─────────────────────────────────────────────────────────
    void downloadArchive();
    void handleArchiveFinished(QNetworkReply *reply);
    void startExtraction();
    void extractZipChunk();
    void startTarExtraction();

    // ── Mojang ──────────────────────────────────────────────────────────
    void handleManifest(const QByteArray &payload);
    bool prepareTree(const QJsonObject &files);
    void pump();
    void startTask(JavaFileTask task);
    void handleFileFinished(const std::shared_ptr<JavaActiveDownload> &context);
    bool retryOrFail(JavaFileTask task, const QString &reason);
    bool applyLinks();

    QNetworkAccessManager *m_network;

    JavaRuntimeEntry m_entry;
    QString m_label;
    QString m_targetDir;
    QString m_archivePath;

    QString m_stage;
    QString m_currentFile;
    qint64 m_bytesSettled = 0;
    qint64 m_bytesTotal = 0;

    // Шаги распаковки: у архива — записи внутри него, у Mojang — файлы дерева.
    int m_stepsDone = 0;
    int m_stepsTotal = 0;

    QNetworkReply *m_reply = nullptr;          // архив или манифест
    std::unique_ptr<QSaveFile> m_archiveFile;
    QCryptographicHash m_archiveHash{QCryptographicHash::Sha256};

    QProcess *m_tar = nullptr;
    QTimer *m_extractTimer;
    QTimer *m_progressTimer;
    bool m_progressDirty = false;

    // Распаковка zip по кускам: держать GUI-поток занятым на всю сотню
    // мегабайт нельзя, а поток ради одной операции заводить незачем.
    std::unique_ptr<ZipExtraction> m_zip;

    QQueue<JavaFileTask> m_queue;
    QList<std::shared_ptr<JavaActiveDownload>> m_active;
    QList<QPair<QString, QString>> m_links;   // {абсолютный путь ссылки, цель}

    bool m_running = false;
    bool m_canceling = false;
};
