/*
    SPDX-FileCopyrightText: 2016 Dan Leinir Turthra Jensen <admin@leinir.dk>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#ifndef ENGINE_H
#define ENGINE_H

#include <QObject>
#include <QQmlListProperty>

#include "categoriesmodel.h"
#include "enginebase.h"
#include "entry.h"
#include "errorcode.h"
#include "provider.h"
#include "searchpresetmodel.h"
#include "transaction.h"

class EnginePrivate;

/**
 * KNSCore::EngineBase for interfacing with QML
 *
 * @see ItemsModel
 */
class Engine : public KNSCore::EngineBase
{
    Q_OBJECT
    Q_PROPERTY(QString configFile READ configFile WRITE setConfigFile NOTIFY configFileChanged)
    Q_PROPERTY(bool isLoading READ isLoading NOTIFY busyStateChanged)
    Q_PROPERTY(bool needsLazyLoadSpinner READ needsLazyLoadSpinner NOTIFY busyStateChanged)
    Q_PROPERTY(bool hasAdoptionCommand READ hasAdoptionCommand NOTIFY configFileChanged)
    Q_PROPERTY(QString name READ name NOTIFY configFileChanged)
    Q_PROPERTY(bool isValid READ isValid NOTIFY configFileChanged)

    Q_PROPERTY(CategoriesModel *categories READ categories NOTIFY categoriesChanged)
    Q_PROPERTY(QStringList categoriesFilter READ categoriesFilter WRITE setCategoriesFilter RESET resetCategoriesFilter NOTIFY categoriesFilterChanged)
    Q_PROPERTY(KNSCore::Provider::Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
    Q_PROPERTY(KNSCore::Filter filter2 READ filter2 WRITE setFilter2 NOTIFY filterChanged)
    Q_PROPERTY(KNSCore::Provider::SortMode sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
    Q_PROPERTY(KNSCore::SortMode sortOrder2 READ sortOrder2 WRITE setSortOrder2 NOTIFY sortOrderChanged)
    Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm RESET resetSearchTerm NOTIFY searchTermChanged)
    Q_PROPERTY(SearchPresetModel *searchPresetModel READ searchPresetModel NOTIFY searchPresetModelChanged)

    /**
     * Current state of the engine, the state con contain multiple operations
     * an empty BusyState represents the idle status
     * @since 5.74
     */
    Q_PROPERTY(BusyState busyState READ busyState WRITE setBusyState NOTIFY busyStateChanged)
    Q_PROPERTY(QString busyMessage READ busyMessage NOTIFY busyStateChanged)
public:
    explicit Engine(QObject *parent = nullptr);
    ~Engine() override;
    Q_DISABLE_COPY_MOVE(Engine)

    enum class BusyOperation {
        Initializing = 1,
        LoadingData,
        LoadingPreview,
        InstallingEntry,
    };
    Q_DECLARE_FLAGS(BusyState, BusyOperation)
    Q_ENUM(BusyOperation)

    enum EntryEvent { // TODO KF6 remove in favor of using NewStuff.Entry values
        UnknownEvent = KNSCore::Entry::UnknownEvent,
        StatusChangedEvent = KNSCore::Entry::StatusChangedEvent,
        AdoptedEvent = KNSCore::Entry::AdoptedEvent,
        DetailsLoadedEvent = KNSCore::Entry::DetailsLoadedEvent,
    };
    Q_ENUM(EntryEvent)

    QString configFile() const;
    void setConfigFile(const QString &newFile);
    Q_SIGNAL void configFileChanged();

    Engine::BusyState busyState() const;
    QString busyMessage() const;
    void setBusyState(Engine::BusyState state);

    /**
     * Signal gets emitted when the busy state changes
     * @since 5.74
     */
    Q_SIGNAL void busyStateChanged();

    /**
     * Whether or not the engine is performing its initial loading operations
     * @since 5.65
     */
    bool isLoading() const
    {
        // When installing entries, we don't want to block the UI
        return busyState().toInt() != 0 && ((busyState() & BusyOperation::InstallingEntry) != BusyOperation::InstallingEntry);
    }

    CategoriesModel *categories() const;
    Q_SIGNAL void categoriesChanged();

    QStringList categoriesFilter() const;
    void setCategoriesFilter(const QStringList &newCategoriesFilter);
    Q_INVOKABLE void resetCategoriesFilter()
    {
        setCategoriesFilter(categoriesFilter());
    }
    Q_SIGNAL void categoriesFilterChanged();

#if KNEWSTUFFCORE_ENABLE_DEPRECATED_SINCE(6, 9)
    /// @deprecated since 6.9 Use filter2
    KNEWSTUFFCORE_DEPRECATED_VERSION(6, 9, "Use filter2")
    KNSCore::Provider::Filter filter() const;
#endif
#if KNEWSTUFFCORE_ENABLE_DEPRECATED_SINCE(6, 9)
    /// @deprecated since 6.9 Use setFilter2
    KNEWSTUFFCORE_DEPRECATED_VERSION(6, 9, "Use setFilter2")
    void setFilter(KNSCore::Provider::Filter filter);
#endif
    [[nodiscard]] KNSCore::Filter filter2() const;
    void setFilter2(KNSCore::Filter filter);
    Q_SIGNAL void filterChanged();

#if KNEWSTUFFCORE_ENABLE_DEPRECATED_SINCE(6, 9)
    /// @deprecated since 6.9 Use sortOrder2
    KNEWSTUFFCORE_DEPRECATED_VERSION(6, 9, "Use sortOrder2")
    KNSCore::Provider::SortMode sortOrder() const;
#endif
#if KNEWSTUFFCORE_ENABLE_DEPRECATED_SINCE(6, 9)
    /// @deprecated since 6.9 Use setSortOrder2
    KNEWSTUFFCORE_DEPRECATED_VERSION(6, 9, "Use setSortOrder2")
    void setSortOrder(KNSCore::Provider::SortMode newSortOrder);
#endif
    [[nodiscard]] KNSCore::SortMode sortOrder2() const;
    void setSortOrder2(KNSCore::SortMode newSortOrder);
    Q_SIGNAL void sortOrderChanged();

    QString searchTerm() const;
    void setSearchTerm(const QString &newSearchTerm);
    Q_INVOKABLE void resetSearchTerm()
    {
        setSearchTerm(QString());
    }
    Q_SIGNAL void searchTermChanged();

    SearchPresetModel *searchPresetModel() const;
    Q_SIGNAL void searchPresetModelChanged();

    Q_INVOKABLE void updateEntryContents(const KNSCore::Entry &entry);
    Q_INVOKABLE KNSCore::Entry __createEntry(const QString &providerId, const QString &entryId)
    {
        KNSCore::Entry e;
        e.setProviderId(providerId);
        e.setUniqueId(entryId);
        return e;
    }

    bool isValid();
    void reloadEntries();

    void loadPreview(const KNSCore::Entry &entry, KNSCore::Entry::PreviewType type);

    /**
     * Adopt an entry using the adoption command. This will also take care of displaying error messages
     * @param entry Entry that should be adopted
     * @see signalErrorCode
     * @see signalEntryEvent
     * @since 5.77
     */
    Q_INVOKABLE void adoptEntry(const KNSCore::Entry &entry);

#if KNEWSTUFFCORE_ENABLE_DEPRECATED_SINCE(6, 9)
    /**
     * Installs an entry's payload file. This includes verification, if
     * necessary, as well as decompression and other steps according to the
     * application's *.knsrc file.
     *
     * @param entry Entry to be installed
     *
     * @see signalInstallationFinished
     * @see signalInstallationFailed
     * @deprecated since 6.9, use installLatest or installLinkId instead
     */
    KNEWSTUFFCORE_DEPRECATED_VERSION(6, 9, "use installLatest or installLinkId instead")
    Q_INVOKABLE void install(const KNSCore::Entry &entry, int linkId = 1);
#endif

    /**
     * Performs an install on the given @p entry
     *
     * @param linkId specifies which of the assets we want to see installed.
     * @since 6.9
     */
    Q_INVOKABLE void installLinkId(const KNSCore::Entry &entry, quint8 linkId);

    /**
     * Performs an install of the latest version on the given @p entry
     *
     * The latest version is determined using heuristics. If you want tight control over which offering gets installed
     * you need to use installLinkId and manually figure out the id.
     *
     * @since 6.9
     */
    Q_INVOKABLE void installLatest(const KNSCore::Entry &entry);

    /**
     * Uninstalls an entry. It reverses the steps which were performed
     * during the installation.
     *
     * @param entry The entry to uninstall
     */
    Q_INVOKABLE void uninstall(const KNSCore::Entry &entry);

    void requestMoreData();

    Q_INVOKABLE void revalidateCacheEntries();
    Q_INVOKABLE void restoreSearch();
    Q_INVOKABLE void storeSearch();
Q_SIGNALS:
    void signalResetView();

    /**
     * This is fired for events related directly to a single Entry instance
     * The intermediate states Updating and Installing are not forwarded. In case you
     * need those you have to listen to the signals of the KNSCore::Engine instance of the engine property.
     *
     * As an example, if you need to know when the status of an entry changes, you might write:
     \code
        function onEntryEvent(entry, event) {
            if (event == NewStuff.Engine.StatusChangedEvent) {
                myModel.ghnsEntryChanged(entry);
            }
        }
     \endcode
     *
     * nb: The above example is also how one would port a handler for the old changedEntries signal
     *
     * @see Entry::EntryEvent for details on which specific event is being notified
     */
    void entryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event);

    /**
     * Fires in the case of any critical or serious errors, such as network or API problems.
     * This forwards the signal from KNSCore::Engine::signalErrorCode, but with QML friendly
     * enumerations.
     * @param errorCode Represents the specific type of error which has occurred
     * @param message A human-readable message which can be shown to the end user
     * @param metadata Any additional data which might be helpful to further work out the details of the error (see KNSCore::Entry::ErrorCode for the
     * metadata details)
     * @see KNSCore::Engine::signalErrorCode
     * @since 5.84
     */
    void errorCode(KNSCore::ErrorCode::ErrorCode errorCode, const QString &message, const QVariant &metadata);

    void entryPreviewLoaded(const KNSCore::Entry &, KNSCore::Entry::PreviewType);

    void signalEntriesLoaded(const KNSCore::Entry::List &entries); ///@internal
    void signalEntryEvent(const KNSCore::Entry &entry, KNSCore::Entry::EntryEvent event); ///@internal

private:
    bool init(const QString &configfile) override;
    void updateStatus() override;
    bool needsLazyLoadSpinner();
    Q_SIGNAL void signalEntryPreviewLoaded(const KNSCore::Entry &, KNSCore::Entry::PreviewType);
    void registerTransaction(KNSCore::Transaction *transactions);
    void doRequest();
    const std::unique_ptr<EnginePrivate> d;
    KNSCore::EngineBasePrivate *dd;
};

#endif // ENGINE_H
