// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2

#pragma once

#include <QAbstractListModel>
#include <QDateTime>
#include <QList>
#include <QSet>
#include <QString>
#include <QTimer>
#include <QVariantMap>
#include <qqmlregistration.h>

#include <KConfigWatcher>
#include <KSharedConfig>

class GameLauncherProvider : public QAbstractListModel
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON

    Q_PROPERTY(int count READ count NOTIFY countChanged)
    Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
    Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged)
    Q_PROPERTY(QString sourceFilter READ sourceFilter WRITE setSourceFilter NOTIFY sourceFilterChanged)
    Q_PROPERTY(bool overlayEnabled READ overlayEnabled WRITE setOverlayEnabled NOTIFY overlayEnabledChanged)
    Q_PROPERTY(bool mangohudAvailable READ mangohudAvailable NOTIFY mangohudAvailableChanged)
    Q_PROPERTY(bool steamAvailable READ steamAvailable NOTIFY launcherAvailabilityChanged)
    Q_PROPERTY(bool lutrisAvailable READ lutrisAvailable NOTIFY launcherAvailabilityChanged)
    Q_PROPERTY(bool heroicAvailable READ heroicAvailable NOTIFY launcherAvailabilityChanged)
    Q_PROPERTY(int fpsLimit READ fpsLimit WRITE setFpsLimit NOTIFY fpsLimitChanged)
    Q_PROPERTY(bool launchPending READ launchPending NOTIFY launchPendingChanged)
    Q_PROPERTY(QString pendingLaunchName READ pendingLaunchName NOTIFY launchPendingChanged)
    Q_PROPERTY(QString lastLaunchError READ lastLaunchError NOTIFY lastLaunchErrorChanged)

public:
    explicit GameLauncherProvider(QObject *parent = nullptr);

    enum Roles {
        NameRole = Qt::UserRole + 1,
        IconRole,
        SourceRole, // "desktop", "waydroid", "steam", "flatpak"
        StorageIdRole, // .desktop file name or launch URI
        LaunchCommandRole,
        LaunchMethodRole,
        ArtworkRole, // path to banner/grid image (empty if none)
        LastPlayedTextRole,
        PinnedRole,
    };
    Q_ENUM(Roles)

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QHash<int, QByteArray> roleNames() const override;

    int count() const;
    bool loading() const;
    QString filterString() const;
    void setFilterString(const QString &filter);
    QString sourceFilter() const;
    void setSourceFilter(const QString &source);
    bool overlayEnabled() const;
    void setOverlayEnabled(bool enabled);
    bool mangohudAvailable() const;
    bool steamAvailable() const;
    bool lutrisAvailable() const;
    bool heroicAvailable() const;
    int fpsLimit() const;
    void setFpsLimit(int limit);
    bool launchPending() const;
    QString pendingLaunchName() const;
    QString lastLaunchError() const;

    Q_INVOKABLE void refresh();
    Q_INVOKABLE void launch(int index);
    Q_INVOKABLE void launchByStorageId(const QString &storageId);
    Q_INVOKABLE QVariantMap gameDetails(const QString &storageId) const;
    Q_INVOKABLE bool openSourceApp(const QString &source);
    Q_INVOKABLE void clearLastPlayed(const QString &storageId);
    Q_INVOKABLE QVariantList recentGames(int limit = 5) const;
    Q_INVOKABLE void clearPendingLaunch();
    Q_INVOKABLE void clearLastLaunchError();
    Q_INVOKABLE void togglePin(const QString &storageId);
    Q_INVOKABLE int perGameFpsLimit(const QString &storageId) const;
    Q_INVOKABLE void setPerGameFpsLimit(const QString &storageId, int limit);
    Q_INVOKABLE int perGameOverlayState(const QString &storageId) const;
    Q_INVOKABLE void setPerGameOverlayState(const QString &storageId, int state);

Q_SIGNALS:
    void countChanged();
    void loadingChanged();
    void filterStringChanged();
    void sourceFilterChanged();
    void overlayEnabledChanged();
    void mangohudAvailableChanged();
    void launcherAvailabilityChanged();
    void fpsLimitChanged();
    void launchPendingChanged();
    void lastLaunchErrorChanged();
    void gameLaunched(const QString &name);
    void gameLaunchFailed(const QString &name, const QString &error);
    void recentGamesChanged();

private:
    struct GameEntry {
        QString name;
        QString icon;
        QString source;
        QString storageId;
        QString launchCommand;
        QString artwork;
        QDateTime lastPlayed;
    };

    void loadDesktopGames();
    void loadSteamGames();
    void loadLutrisGames();
    void loadHeroicGames();
    void deduplicateGames();
    void loadRecentTimestamps();
    void saveRecentTimestamp(const QString &storageId, const QDateTime &when);
    void applyFilter();
    void launchEntry(GameEntry &entry);
    bool launchWithMangohud(const QString &program, const QStringList &args, bool overlayEnabled, int fpsLimit, qint64 *pid = nullptr);
    QString launchMethodForEntry(const GameEntry &entry) const;
    QString formatLastPlayed(const QDateTime &when) const;
    // Returns the current m_allGames index for the storage id.
    // Callers must re-lookup after any mutation that can rebuild or reorder the list.
    int findEntryIndexByStorageId(const QString &storageId) const;
    void markLaunchSucceeded(const QString &storageId, const QString &name);
    void markLaunchFailed(const QString &name, const QString &error);
    void setPendingLaunch(const QString &name);
    void loadPinnedGames();

    QList<GameEntry> m_allGames;
    QList<GameEntry> m_games; // filtered view
    QString m_filterString;
    QString m_sourceFilter; // empty = all, or a specific source such as "desktop" or "waydroid"
    KSharedConfigPtr m_config;
    bool m_loading = false;
    bool m_overlayEnabled = false;
    int m_fpsLimit = 0;
    bool m_mangohudAvailable = false;
    QString m_mangohudPath;
    bool m_steamAvailable = false;
    bool m_lutrisAvailable = false;
    bool m_heroicAvailable = false;

    static bool detectLauncher(const QString &executable, const QStringList &flatpakAppIds, const QStringList &dataDirs);
    QSet<QString> m_pinnedGames;
    bool m_launchPending = false;
    QString m_pendingLaunchName;
    QString m_lastLaunchError;
    QTimer m_pendingLaunchTimer;
    KConfigWatcher::Ptr m_configWatcher;
};
