#pragma once

#include "minecraftversion.h"

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QQmlEngine>
#include <QStringList>
#include <QVariantMap>

struct AuthResult;
class AuthService;
class GameLauncher;
class VersionManifestService;
class VersionInstaller;
class ModLoaderVersionService;
class ModLoaderInstaller;
class BuildSwitcher;
class JavaRuntimeService;
class JavaInstaller;

class LauncherBackend : public QObject
{
    Q_OBJECT
    QML_ELEMENT

    Q_PROPERTY(QStringList profileNames READ profileNames NOTIFY profilesChanged)
    Q_PROPERTY(QStringList customBuildNames READ customBuildNames NOTIFY customBuildsChanged)

    // Сборка, которую запускает кнопка игры. Хранится по id сборки, а не по
    // индексу: удаление соседней записи не должно переназначать активную.
    Q_PROPERTY(int activeBuildIndex READ activeBuildIndex WRITE setActiveBuildIndex
                   NOTIFY activeBuildChanged)
    Q_PROPERTY(QString activeBuildName READ activeBuildName NOTIFY activeBuildChanged)
    Q_PROPERTY(QStringList installedVersions READ installedVersions NOTIFY installedVersionsChanged)
    Q_PROPERTY(bool gameRunning READ gameRunning NOTIFY gameRunningChanged)

    // Идёт архивация/распаковка .minecraft при смене сборки. Отдельно от busy,
    // потому что на это время блокируется ещё и список сборок.
    Q_PROPERTY(bool switching READ switching NOTIFY switchChanged)
    Q_PROPERTY(double switchProgress READ switchProgress NOTIFY switchChanged)
    Q_PROPERTY(QString switchStage READ switchStage NOTIFY switchChanged)
    Q_PROPERTY(QString switchStatus READ switchStatus NOTIFY switchChanged)
    Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)

    // Объединённый список для выпадающего меню версий: установленные, разделитель,
    // затем весь каталог Mojang.
    Q_PROPERTY(QVariantList versionCatalog READ versionCatalog NOTIFY versionCatalogChanged)
    Q_PROPERTY(bool catalogLoading READ catalogLoading NOTIFY catalogLoadingChanged)

    // Сборки Java для окна выбора: скачиваемые из сети плюс те, что уже лежат
    // в папке лаунчера.
    Q_PROPERTY(QVariantList javaCatalog READ javaCatalog NOTIFY javaCatalogChanged)
    Q_PROPERTY(bool javaCatalogLoading READ javaCatalogLoading NOTIFY javaCatalogLoadingChanged)

    // Одного сигнала на все свойства загрузки достаточно: установщик уже
    // ограничивает частоту, а QML всё равно перечитывает их разом.
    Q_PROPERTY(bool downloading READ downloading NOTIFY downloadChanged)
    Q_PROPERTY(double downloadProgress READ downloadProgress NOTIFY downloadChanged)
    Q_PROPERTY(QString downloadVersion READ downloadVersion NOTIFY downloadChanged)
    Q_PROPERTY(QString downloadStatus READ downloadStatus NOTIFY downloadChanged)
    Q_PROPERTY(qint64 downloadBytesDone READ downloadBytesDone NOTIFY downloadChanged)
    Q_PROPERTY(qint64 downloadBytesTotal READ downloadBytesTotal NOTIFY downloadChanged)

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

    QStringList profileNames() const;
    QStringList customBuildNames() const;
    int activeBuildIndex() const;
    void setActiveBuildIndex(int index);
    QString activeBuildName() const;
    QStringList installedVersions() const;
    bool gameRunning() const;
    bool switching() const;
    double switchProgress() const;
    QString switchStage() const;
    QString switchStatus() const;
    // Пока идёт загрузка версии, лаунчер тоже занят: кнопка запуска гаснет.
    bool busy() const;

    QVariantList versionCatalog() const;
    bool catalogLoading() const;

    QVariantList javaCatalog() const;
    bool javaCatalogLoading() const;

    bool downloading() const;
    double downloadProgress() const;
    QString downloadVersion() const;
    QString downloadStatus() const;
    qint64 downloadBytesDone() const;
    qint64 downloadBytesTotal() const;

    Q_INVOKABLE void addProfile(const QString &name,
                                const QString &login,
                                const QString &password,
                                const QString &authType = QStringLiteral("offline"));
    Q_INVOKABLE void addCustomBuild(const QString &name,
                                    const QString &serverUrl,
                                    const QString &minecraftVersion = QString());
    Q_INVOKABLE void updateProfile(int index,
                                   const QString &name,
                                   const QString &login,
                                   const QString &password,
                                   const QString &authType = QStringLiteral("offline"));
    // Мержит только присланные ключи: name, serverUrl, minecraftVersion,
    // loader, loaderVersion, resolvedVersionId.
    Q_INVOKABLE void updateCustomBuild(int index, const QVariantMap &fields);
    Q_INVOKABLE QVariantMap profileAt(int index) const;
    Q_INVOKABLE QVariantMap customBuildAt(int index) const;
    Q_INVOKABLE void removeProfile(int index);
    // Что именно потеряется при удалении — для текста предупреждения.
    Q_INVOKABLE QVariantMap customBuildRemovalInfo(int index) const;
    Q_INVOKABLE void removeCustomBuild(int index);

    // Докачать то, чего не хватает выбранной сборке. Отдельной кнопкой, потому
    // что карточка сборки сохраняет правки по ходу редактирования.
    Q_INVOKABLE void installCustomBuild(int index);

    // Главная кнопка. Проверяет выбор профиля и версии, комплектность .minecraft,
    // при необходимости авторизуется и стартует игру.
    Q_INVOKABLE void launchGame(int profileIndex, int buildIndex);
    Q_INVOKABLE void submitTwoFactorCode(const QString &code);
    Q_INVOKABLE void cancelPendingLaunch();
    Q_INVOKABLE void stopGame();

    // Проверка без запуска — для подсказки в интерфейсе.
    Q_INVOKABLE QStringList checkInstallation(int buildIndex) const;

    // Вызывается при открытии выпадающего списка версий: свежий кэш — без сети.
    Q_INVOKABLE void refreshVersionCatalog(bool force = false);
    Q_INVOKABLE bool isVersionInstalled(const QString &versionId) const;
    Q_INVOKABLE void installVersion(const QString &versionId);
    // Что потеряется при удалении версии — для текста предупреждения.
    Q_INVOKABLE QVariantMap versionRemovalInfo(const QString &versionId) const;
    Q_INVOKABLE void removeVersion(const QString &versionId);
    Q_INVOKABLE void cancelDownload();

    // Версии модлоадеров для выбранной версии игры. Возвращает список карт
    // {version, label, recommended, stable}; несовместимых строк в нём нет —
    // отбор заложен в сам источник данных.
    Q_INVOKABLE QVariantList loaderVersions(const QString &loaderKey,
                                            const QString &gameVersion) const;
    Q_INVOKABLE void refreshLoaderVersions(const QString &loaderKey,
                                           const QString &gameVersion,
                                           bool force = false);
    Q_INVOKABLE bool loaderVersionsLoading(const QString &loaderKey,
                                           const QString &gameVersion) const;
    // Ставит модлоадер, выбранный в сборке, и записывает получившийся профиль
    // в resolvedVersionId.
    Q_INVOKABLE void installLoaderForBuild(int index);

    Q_INVOKABLE QVariantMap settings() const;
    Q_INVOKABLE void updateSettings(const QVariantMap &values);
    Q_INVOKABLE QStringList detectedJava() const;

    // ── Сборки Java ─────────────────────────────────────────────────────
    // Вызывается при открытии окна выбора: свежий кэш — без сети.
    Q_INVOKABLE void refreshJavaCatalog(bool force = false);
    Q_INVOKABLE void installJavaRuntime(const QString &runtimeId);
    Q_INVOKABLE void removeJavaRuntime(const QString &runtimeId);
    // Пусто — сборки с таким id в папке лаунчера нет.
    Q_INVOKABLE QVariantMap javaRuntimeInfo(const QString &runtimeId) const;
    // Минимальная Java для версии игры выбранной сборки; 0 — версия не выбрана.
    Q_INVOKABLE int requiredJavaMajor(int buildIndex) const;

    Q_INVOKABLE void openMinecraftFolder();
    Q_INVOKABLE void openGameFolder();

signals:
    void profilesChanged();
    void customBuildsChanged();
    void activeBuildChanged();
    void switchChanged();
    void loaderVersionsChanged(const QString &loaderKey, const QString &gameVersion);
    void installedVersionsChanged();
    void settingsChanged();
    void gameRunningChanged();
    void busyChanged();
    void versionCatalogChanged();
    void catalogLoadingChanged();
    void javaCatalogChanged();
    void javaCatalogLoadingChanged();
    void downloadChanged();

    // Сборка Java установлена: окно настроек обновляет подпись, не переоткрываясь.
    void javaRuntimeInstalled(const QString &runtimeId);

    void launchProgress(const QString &message);
    void launched(const QString &profileName, const QString &buildName, const QString &serverUrl);
    void launchError(const QString &message);
    void twoFactorRequired(const QString &profileName);
    void gameOutput(const QString &line);
    void gameFinished(int exitCode, bool crashed);

private:
    struct Profile
    {
        int id = 0;
        QString name, login, password;
        QString authType = QStringLiteral("offline");   // offline | elyby
        QString clientToken, accessToken, uuid, resolvedName, userType;
    };
    // Пользовательская сборка: имя, сервер, версия Minecraft и, начиная со
    // второго этапа, выбранный модлоадер.
    struct CustomBuild
    {
        int id = 0;
        QString name, serverUrl;
        QString minecraftVersion;    // 1.20.1
        QString loader;              // "" | forge | fabric | neoforge | quilt
        QString loaderVersion;       // 47.4.0 | 0.19.3
        QString resolvedVersionId;   // versions/<id>, который реально запускается
    };

    void loadData();
    void loadSettings();
    void ensureDataFiles();
    void saveProfiles();
    void saveCustomBuilds();
    void saveSettings();

    // Чтение файлов лаунчера. Отсутствие файла — норма (первый запуск), а вот
    // повреждённое содержимое отводится в .bak, чтобы файл создался заново.
    QJsonArray readArrayFile(const QString &path);
    QJsonObject readObjectFile(const QString &path);
    bool moveAsideBroken(const QString &path, const QString &reason);

    // Проверяет папку лаунчера и пишет документ. Об ошибке сообщает через launchError.
    bool writeJsonFile(const QString &path, const QJsonDocument &document);

    QString gameDir() const;
    // id профиля в <gameDir>/versions, который запускает эта сборка.
    QString launchVersionIdOf(const CustomBuild &build) const;
    int indexOfBuildId(int buildId) const;

    // Перекладывает <root>/versions.json в customBuilds.json при первом запуске
    // после переименования. true — миграция прошла, читать нужно новый файл.
    bool migrateLegacyBuildsFile();

    void invalidateCatalog();
    void rebuildCatalog() const;
    void invalidateJavaCatalog();
    void rebuildJavaCatalog() const;
    // Панель загрузки занята установкой Java, а не версии или модлоадера.
    bool javaDownloadShown() const;
    // Путь к java выбранной сборки из папки лаунчера. Пусто — сборка не выбрана
    // или ещё не скачана; тогда в дело идёт javaPath и автопоиск.
    QString managedJavaPath() const;
    // Версия выбрана, но её нет в .minecraft — ставим в фоне.
    void maybeInstall(const QString &versionId);

    void setBusy(bool busy);
    void failLaunch(const QString &message);
    void beginAuthentication();
    void continueLaunch(const AuthResult &auth);

    QList<Profile> m_profiles;
    QList<CustomBuild> m_builds;
    QVariantMap m_settings;
    // Проблемы хранилища, замеченные на старте: показываются одним сообщением,
    // когда интерфейс уже подключился к сигналам.
    QStringList m_storageIssues;
    int m_nextProfileId = 1;
    int m_nextBuildId = 1;

    AuthService *m_auth;
    GameLauncher *m_launcher;
    VersionManifestService *m_manifest;
    VersionInstaller *m_installer;
    ModLoaderVersionService *m_loaderMeta;
    ModLoaderInstaller *m_loaderInstaller;
    BuildSwitcher *m_switcher;
    JavaRuntimeService *m_javaMeta;
    JavaInstaller *m_javaInstaller;

    // Сборка, для которой сейчас ставится модлоадер: по её id находим запись,
    // когда установщик отчитается.
    int m_loaderInstallBuildId = -1;

    // Каталог версий пересобирается только по изменениям: QML читает свойство
    // помногу раз за кадр, пока открыт список.
    mutable QVariantList m_catalog;
    mutable bool m_catalogDirty = true;

    mutable QVariantList m_javaCatalog;
    mutable bool m_javaCatalogDirty = true;

    bool m_busy = false;

    // Состояние запуска, начатого до асинхронной авторизации.
    int m_pendingProfile = -1;
    int m_pendingBuild = -1;
    MinecraftVersion m_pendingVersionData;
    QString m_pendingInjectorPath;
};
