/*
    SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#ifndef PLASMA_ABSTRACTRUNNER_H
#define PLASMA_ABSTRACTRUNNER_H

#include <QIcon>
#include <QObject>
#include <QStringList>

#include <KConfigGroup>
#include <KPluginInfo>
#include <KPluginMetaData>
#include <KService>

#include <plasma/plasma_export.h> // for PLASMA_ENABLE_DEPRECATED_SINCE
#include <plasma_version.h>

#include "krunner_export.h"
#include "querymatch.h"
#include "runnercontext.h"
#include "runnersyntax.h"

class QAction;
class QMimeData;
class QRegularExpression;

namespace Plasma
{
class DataEngine;
class Package;
class QueryMatch;
class AbstractRunnerPrivate;

enum RunnerReturnPluginMetaDataConstant { RunnerReturnPluginMetaData }; // KF6: remove again

/**
 * @class AbstractRunner abstractrunner.h <KRunner/AbstractRunner>
 *
 * @short An abstract base class for Plasma Runner plugins.
 *
 * Be aware that runners have to be thread-safe. This is due to the fact that
 * each runner is executed in its own thread for each new term. Thus, a runner
 * may be executed more than once at the same time. See match() for details.
 * To let krunner expose a global shortcut for the single runner query mode, the runner
 * must set the "X-Plasma-AdvertiseSingleRunnerMode" key to true in the .desktop file
 * and set a default syntax. See setDefaultSyntax() for details.
 *
 */
class KRUNNER_EXPORT AbstractRunner : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool matchingSuspended READ isMatchingSuspended WRITE suspendMatching NOTIFY matchingSuspended)
    Q_PROPERTY(QString id READ id)
    Q_PROPERTY(QString description READ description)
    Q_PROPERTY(QString name READ name)
    Q_PROPERTY(QIcon icon READ icon)
    Q_PROPERTY(int minLetterCount READ minLetterCount WRITE setMinLetterCount)
    Q_PROPERTY(QRegularExpression matchRegex READ matchRegex WRITE setMatchRegex)

public:
    /** Specifies a nominal speed for the runner */
    enum Speed {
        SlowSpeed,
        NormalSpeed,
    };

    /** Specifies a priority for the runner */
    enum Priority {
        LowestPriority = 0,
        LowPriority,
        NormalPriority,
        HighPriority,
        HighestPriority,
    };

    /** An ordered list of runners */
    typedef QList<AbstractRunner *> List;

    virtual ~AbstractRunner();

    /**
     * This is the main query method. It should trigger creation of
     * QueryMatch instances through RunnerContext::addMatch and
     * RunnerContext::addMatches. It is called internally by performMatch().
     *
     * If the runner can run precisely the requested term (RunnerContext::query()),
     * it should create an exact match by setting the type to RunnerContext::ExactMatch.
     * The first runner that creates a QueryMatch will be the
     * default runner. Other runner's matches will be suggested in the
     * interface. Non-exact matches should be offered via RunnerContext::PossibleMatch.
     *
     * The match will be activated via run() if the user selects it.
     *
     * Each runner is executed in its own thread. Whenever the user input changes this
     * method is called again. Thus, it needs to be thread-safe. Also, all matches need
     * to be reported once this method returns. Asynchronous runners therefore need
     * to make use of a local event loop to wait for all matches.
     *
     * It is recommended to use local status data in async runners. The simplest way is
     * to have a separate class doing all the work like so:
     *
     * \code
     * void MyFancyAsyncRunner::match(RunnerContext &context)
     * {
     *     QEventLoop loop;
     *     MyAsyncWorker worker(context);
     *     connect(&worker, &MyAsyncWorker::finished, &loop, &MyAsyncWorker::quit);
     *     worker.work();
     *     loop.exec();
     * }
     * \endcode
     *
     * Here MyAsyncWorker creates all the matches and calls RunnerContext::addMatch
     * in some internal slot. It emits the finished() signal once done which will
     * quit the loop and make the match() method return. The local status is kept
     * entirely in MyAsyncWorker which makes match() trivially thread-safe.
     *
     * If a particular match supports multiple actions, set up the corresponding
     * actions in the actionsForMatch method. Do not call any of the action methods
     * within this method!
     *
     * Execution of the correct action should be handled in the run method.
     * @caution This method needs to be thread-safe since KRunner will simply
     * start a new thread for each new term.
     *
     * @warning Returning from this method means to end execution of the runner.
     *
     * @sa run(), RunnerContext::addMatch, RunnerContext::addMatches, QueryMatch
     */
    virtual void match(Plasma::RunnerContext &context);

    /**
     * Triggers a call to match. This will call match() internally.
     *
     * @param context the search context used in executing this match.
     */
    void performMatch(Plasma::RunnerContext &context);

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 71)
    /**
     * If the runner has options that the user can interact with to modify
     * what happens when run or one of the actions created in match
     * is called, the runner should return true
     * @deprecated Since 5.0, this feature has been defunct
     */
    KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed")
    bool hasRunOptions();
#endif

#if KRUNNER_BUILD_DEPRECATED_SINCE(5, 71)
    /**
     * If hasRunOptions() returns true, this method may be called to get
     * a widget displaying the options the user can interact with to modify
     * the behaviour of what happens when a given match is selected.
     *
     * @param widget the parent of the options widgets.
     * @deprecated Since 5.0, this feature has been defunct
     */
    KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed")
    virtual void createRunOptions(QWidget *widget);
#endif

    /**
     * Called whenever an exact or possible match associated with this
     * runner is triggered.
     *
     * @param context The context in which the match is triggered, i.e. for which
     *                the match was created.
     * @param match The actual match to run/execute.
     */
    virtual void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match);

#if KRUNNER_BUILD_DEPRECATED_SINCE(5, 76)
    /**
     * Return a list of categories that this runner provides. By default
     * this list just contains the runners name. It is used by the runner manager
     * to disable certain runners if all the categories they provide have
     * been disabled.
     *
     * This list of categories is also used to provide a better way to
     * configure the runner instead of the typical on / off switch.
     * @deprecated Since 5.76, feature is unused. You can still set the category property in the QueryMatch
     */
    KRUNNER_DEPRECATED_VERSION(5, 76, "Feature is unused")
    virtual QStringList categories() const;
#endif

#if KRUNNER_BUILD_DEPRECATED_SINCE(5, 76)
    /**
     * Returns the icon which accurately describes the category \p category.
     * This is meant to be used in a configuration dialog when showing
     * all the categories.
     *
     * By default this returns the icon of the AbstractRunner
     * * @deprecated Since 5.0, feature removed
     */
    KRUNNER_DEPRECATED_VERSION_BELATED(5, 76, 5, 0, "No longer use, feature removed")
    virtual QIcon categoryIcon(const QString &category) const;
#endif

    /**
     * The nominal speed of the runner.
     * @see setSpeed
     */
    Speed speed() const;

    /**
     * The priority of the runner.
     * @see setPriority
     */
    Priority priority() const;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 76)
    /**
     * Returns the OR'ed value of all the Information types (as defined in RunnerContext::Type)
     * this runner is not interested in.
     * @return OR'ed value of black listed types
     * @deprecated This feature is deprecated
     */
    KRUNNER_DEPRECATED_VERSION(5, 76, "feature is deprecated")
    RunnerContext::Types ignoredTypes() const;
#endif

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 76)
    /**
     * Sets the types this runner will ignore. If the value from RunnerContext::type() is contained in the ignored types
     * the match() method won't be called. This way there is no unnecessary thread spawned. The same RunnerContext from
     * which the type gets read is later passed into the match(Plasma::RunnerContext &context) method call.
     * @param types OR'ed listed of ignored types
     * @deprecated feature is deprecated. Consider using the minLetterCount and matchRegex properties instead. These
     * properties also prevent thread spawning, but can be used far more precise.
     * If you want to have this kind of optimization for older KRunner versions you could wrap this
     * inside of an version if statement:
     * @code
         #if KRUNNER_VERSION < QT_VERSION_CHECK(5, 76, 0)
         //set ignore types
         #endif
     * @endcode
     * The minLetterCount and matchRegex can be set with a similar version check or added to the desktop file.
     * If an older KRunner version loads such a desktop file these unknown properties are just ignored.
     * @see minLetterCount
     * @see matchRegex
     */
    KRUNNER_DEPRECATED_VERSION(5, 76, "feature is deprecated. Consider using the minLetterCount and matchRegex properties instead")
    void setIgnoredTypes(RunnerContext::Types types);
#endif

    /**
     * @return the user visible engine name for the Runner
     */
    QString name() const;

    /**
     * @return an id string for the Runner
     */
    QString id() const;

    /**
     * @return the description of this Runner
     */
    QString description() const;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 72)
    /**
     * @return the plugin info for this runner
     * @deprecated since 5.72, use metaData(Plasma::RunnerReturnPluginMetaDataConstant) instead, see its API docs
     */
    KRUNNER_DEPRECATED_VERSION(5, 72, "Use metaData(Plasma::RunnerReturnPluginMetaDataConstant) instead, see its API docs")
    KPluginInfo metadata() const;
#endif

    /**
     * @return the plugin metadata for this runner
     *
     * Overload to get non-deprecated metadata format. Use like this:
     * @code
     * KPluginMetaData md = runner->metadata(Plasma::RunnerReturnPluginMetaData);
     * @endcode
     * If you disable the deprecated version using the KRUNNER_DISABLE_DEPRECATED_BEFORE_AND_AT macro,
     * then you can omit Plasma::RunnerReturnPluginMetaDataConstant and use it like this:
     * @code
     * KPluginMetaData md = runner->metadata();
     * @endcode
     *
     * @since 5.72
     */
#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 72)
    KPluginMetaData metadata(RunnerReturnPluginMetaDataConstant) const;
#else
    KPluginMetaData metadata(RunnerReturnPluginMetaDataConstant = RunnerReturnPluginMetaData) const;
#endif
    /**
     * @return the icon for this Runner
     */
    QIcon icon() const;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 65) // not 5.28 here, this KRUNNER visibility control only added at 5.65
#if PLASMA_ENABLE_DEPRECATED_SINCE(5, 28) // Plasma::Package is defined with this condition
    /**
     * Accessor for the associated Package object if any.
     *
     * Note that the returned pointer is only valid for the lifetime of
     * the runner.
     *
     * @return the Package object, which may be invalid
     * @deprecated since 5.28, use KPackage::Package instead, no accessor in this class
     **/
    KRUNNER_DEPRECATED_VERSION(5, 28, "No longer use, feature removed")
    Package package() const;
#endif
#endif

    /**
     * Signal runner to reload its configuration.
     */
    virtual void reloadConfiguration();

    /**
     * @return the syntaxes the runner has registered that it accepts and understands
     * @since 4.3
     */
    QList<RunnerSyntax> syntaxes() const;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 76)
    /**
     * @return the default syntax for the runner or @c nullptr if no default syntax has been defined
     *
     * @since 4.4
     * @deprecated Since 5.76, feature is unused.
     */
    KRUNNER_DEPRECATED_VERSION(5, 76, "No longer use, feature is unused")
    RunnerSyntax *defaultSyntax() const;
#endif

    /**
     * @return true if the runner is currently busy with non-interuptable work, signaling that
     * new threads should not be created for it at this time
     * @since 4.6
     */
    bool isMatchingSuspended() const;

    /**
     * This is the minimum letter count for the query. If the query is shorter than this value
     * and KRunner is not in the singleRunnerMode, the performMatch and consequently match method is not called.
     * This can be set using the X-Plasma-Runner-Min-Letter-Count property or the setMinLetterCount method.
     * @see setMinLetterCount
     * @see match
     * @see performMatch
     * @return minLetterCount property
     * @since 5.75
     */
    int minLetterCount() const;

    /**
     * Set the minLetterCount property
     * @param count
     * @since 5.75
     */
    void setMinLetterCount(int count);

    /**
     * If this regex is set with a not empty pattern it must match the query in
     * order for the performMatch/match being called.
     * Just like the minLetterCount property this check is ignored when the runner is in the singleRunnerMode.
     * In case both the regex and the letter count is set the letter count is checked first.
     * @return matchRegex property
     * @see hasMatchRegex
     * @since 5.75
     */
    QRegularExpression matchRegex() const;

    /**
     * Set the matchRegex property
     * @param regex
     * @since 5.75
     */
    void setMatchRegex(const QRegularExpression &regex);

    /**
     * Constructs internally a regex which requires the query to start with the trigger words.
     * Multiple words are concatenated with or, for instance: "^word1|word2|word3".
     * The trigger words are internally escaped.
     * Also the minLetterCount is set to the shortest word in the list.
     * @since 5.75
     * @see matchRegex
     */
    void setTriggerWords(const QStringList &triggerWords);

    /**
     * If the runner has a valid regex and non empty regex
     * @return hasMatchRegex
     * @since 5.75
     */
    bool hasMatchRegex() const;

Q_SIGNALS:
    /**
     * This signal is emitted when matching is about to commence, giving runners
     * an opportunity to prepare themselves, e.g. loading data sets or preparing
     * IPC or network connections. This method should be fast so as not to cause
     * slow downs. Things that take longer or which should be loaded once and
     * remain extant for the lifespan of the AbstractRunner should be done in init().
     * @see init()
     * @since 4.4
     */
    void prepare();

    /**
     * This signal is emitted when a session of matches is complete, giving runners
     * the opportunity to tear down anything set up as a result of the prepare()
     * method.
     * @since 4.4
     */
    void teardown();

    /**
     * Emitted when the runner enters or exits match suspension
     * @see matchingSuspended
     * @since 4.6
     */
    void matchingSuspended(bool suspended);

protected:
    friend class RunnerManager;
    friend class RunnerManagerPrivate;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 77)
    KRUNNER_DEPRECATED_VERSION(5, 77, "use AbstractRunner(QObject *, const KPluginMetaData &, const QVariantList &)")
    explicit AbstractRunner(QObject *parent = nullptr, const QString &path = QString());
#else
    explicit AbstractRunner(QObject *parent = nullptr, const QString &path = QString()) = delete;
#endif

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 72)
#if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
    /// @deprecated Since 5.72, use AbstractRunner(const KPluginMetaData &, QObject *)
    KRUNNER_DEPRECATED_VERSION(5, 72, "use AbstractRunner(const KPluginMetaData &, QObject *)")
    explicit AbstractRunner(const KService::Ptr service, QObject *parent = nullptr);
#endif
#endif
    /// @since 5.72
    explicit AbstractRunner(const KPluginMetaData &pluginMetaData, QObject *parent = nullptr);
    /// @since 5.77
    AbstractRunner(QObject *parent, const KPluginMetaData &pluginMetaData, const QVariantList &args);
#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 77)
    /// @deprecated Since 5.77, use AbstractRunner(QObject *, const KPluginMetaData &, const QVariantList &)
    KRUNNER_DEPRECATED_VERSION(5, 77, "use AbstractRunner(QObject *, const KPluginMetaData &, const QVariantList &)")
    AbstractRunner(QObject *parent, const QVariantList &args);
#endif

    /**
     * Sets whether or not the runner is available for match requests. Useful to
     * prevent more thread spawning when the thread is in a busy state.
     */
    void suspendMatching(bool suspend);

    /**
     * Provides access to the runner's configuration object.
     */
    KConfigGroup config() const;

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 71)
    /**
     * Sets whether or not the runner has options for matches
     * @deprecated Since 5.0, this feature has been defunct
     */
    KRUNNER_DEPRECATED_VERSION_BELATED(5, 71, 5, 0, "No longer use, feature removed")
    void setHasRunOptions(bool hasRunOptions);
#endif

    /**
     * Sets the nominal speed of the runner. Only slow runners need
     * to call this within their constructor because the default
     * speed is NormalSpeed. Runners that use D-Bus should call
     * this within their constructors.
     */
    void setSpeed(Speed newSpeed);

    /**
     * Sets the priority of the runner. Lower priority runners are executed
     * only after higher priority runners.
     */
    void setPriority(Priority newPriority);

    /**
     * A given match can have more than action that can be performed on it.
     * For example, a song match returned by a music player runner can be queued,
     * added to the playlist, or played.
     *
     * Call this method to add actions that can be performed by the runner.
     * Actions must first be added to the runner's action registry.
     * Note: execution of correct action is left up to the runner.
     */
    virtual QList<QAction *> actionsForMatch(const Plasma::QueryMatch &match);

    /**
     * Creates and then adds an action to the action registry.
     * AbstractRunner assumes ownership of the created action.
     *
     * @param id A unique identifier string
     * @param icon The icon to display
     * @param text The text to display
     * @return the created QAction
     */
    QAction *addAction(const QString &id, const QIcon &icon, const QString &text);

    /**
     * Adds an action to the runner's action registry.
     *
     * The QAction must be created within the GUI thread;
     * do not create it within the match method of AbstractRunner.
     *
     * @param id A unique identifier string
     * @param action The QAction to be stored
     */
    void addAction(const QString &id, QAction *action);

    /**
     * Removes the action from the action registry.
     * AbstractRunner deletes the action once removed.
     *
     * @param id The id of the action to be removed
     */
    void removeAction(const QString &id);

    /**
     * Returns the action associated with the id
     */
    QAction *action(const QString &id) const;

    /**
     * Returns all registered actions
     */
    QHash<QString, QAction *> actions() const;

    /**
     * Clears the action registry.
     * The action pool deletes the actions.
     */
    void clearActions();

    /**
     * Adds a registered syntax that this runner understands. This is used to
     * display to the user what this runner can understand and how it can be
     * used.
     *
     * @param syntax the syntax to register
     * @since 4.3
     */
    void addSyntax(const RunnerSyntax &syntax);

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 76)
    /**
     * Set @p syntax as the default syntax for the runner; the default syntax will be
     * substituted to the empty query in single runner mode. This is also used to
     * display to the user what this runner can understand and how it can be
     * used.
     * The default syntax is automatically added to the list of registered syntaxes, there
     * is no need to add it using addSyntax.
     * Note that there can be only one default syntax; if called more than once, the last
     * call will determine the default syntax.
     * A default syntax (even trivial) is required to advertise single runner mode
     *
     * @param syntax the syntax to register and to set as default
     * @since 4.4
     * @deprecated Since 5.76, feature is unused. Use addSyntax() instead.
     **/
    KRUNNER_DEPRECATED_VERSION(5, 76, "Feature unused, use addSyntax() instead")
    void setDefaultSyntax(const RunnerSyntax &syntax);
#endif

    /**
     * Sets the list of syntaxes; passing in an empty list effectively clears
     * the syntaxes.
     *
     * @param the syntaxes to register for this runner
     * @since 4.3
     */
    void setSyntaxes(const QList<RunnerSyntax> &syns);

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 73)
    /**
     * Loads the given DataEngine
     *
     * Tries to load the data engine given by @p name.  Each engine is
     * only loaded once, and that instance is re-used on all subsequent
     * requests.
     *
     * If the data engine was not found, an invalid data engine is returned
     * (see DataEngine::isValid()).
     *
     * Note that you should <em>not</em> delete the returned engine.
     *
     * @param name Name of the data engine to load
     * @return pointer to the data engine if it was loaded,
     *         or an invalid data engine if the requested engine
     *         could not be loaded
     *
     * @since 4.4
     * @deprecated Since 5.73, DataEngines are deprecated, use e.g. a shared library to provide the data instead.
     */
    KRUNNER_DEPRECATED_VERSION(5, 73, "DataEngines are deprecated, use e.g. a shared library to provide the data instead.")
    Q_INVOKABLE DataEngine *dataEngine(const QString &name) const;
#endif

    /**
     * Reimplement this slot to run any initialization routines on first load.
     * By default, it calls reloadConfiguration(); for scripted Runners this
     * method also sets up the ScriptEngine.
     */
    virtual void init();

    /**
     * Reimplement this slot if you want your runner
     * to support serialization and drag and drop
     * @since 4.5
     */
    virtual QMimeData *mimeDataForMatch(const Plasma::QueryMatch &match);

private:
    AbstractRunnerPrivate *const d;
};

} // Plasma namespace

// clang-format off
#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 72)
// Boilerplate to emit a version-controlled warning about the deprecated macro at leats with GCC
#if KRUNNER_DEPRECATED_WARNINGS_SINCE >= 0x054800 // 5.72.0
#   if defined(__GNUC__)
#       define K_EXPORT_PLASMA_RUNNER_DO_PRAGMA(x) _Pragma (#x)
#       define K_EXPORT_PLASMA_RUNNER_WARNING(x) K_EXPORT_PLASMA_RUNNER_DO_PRAGMA(message(#x))
#   else
#       define K_EXPORT_PLASMA_RUNNER_WARNING(x)
#   endif
#else
#   define K_EXPORT_PLASMA_RUNNER_WARNING(x)
#endif
/**
 * @relates Plasma::AbstractRunner
 *
 * Registers a runner plugin.
 *
 * @deprecated Since 5.72, use K_EXPORT_PLASMA_RUNNER_WITH_JSON(classname, jsonFile) instead
 */
#define K_EXPORT_PLASMA_RUNNER( libname, classname )     \
K_EXPORT_PLASMA_RUNNER_WARNING("Deprecated. Since 5.72, use K_EXPORT_PLASMA_RUNNER_WITH_JSON(classname, jsonFile) instead") \
K_PLUGIN_FACTORY(factory, registerPlugin<classname>();) \
K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION)
#endif

/**
 * @relates Plasma::AbstractRunner
 *
 * Registers a runner plugin with JSON metadata.
 *
 * @param classname name of the AbstractRunner derived class.
 * @param jsonFile name of the JSON file to be compiled into the plugin as metadata
 *
 * @since 5.72
 */
#define K_EXPORT_PLASMA_RUNNER_WITH_JSON(classname, jsonFile) \
    K_PLUGIN_FACTORY_WITH_JSON(classname ## Factory, jsonFile, registerPlugin<classname>();) \
    K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION)

#if KRUNNER_ENABLE_DEPRECATED_SINCE(5, 75)
/**
 * These plugins are Used by the plugin selector dialog to show
 * configuration options specific to this runner. These options
 * must be runner global and not pertain to a specific match.
 * @deprecated Since 5.0, use K_PLUGIN_FACTORY directly
 */
#define K_EXPORT_RUNNER_CONFIG( name, classname )     \
K_PLUGIN_FACTORY(ConfigFactory, registerPlugin<classname>();) \
K_EXPORT_PLUGIN_VERSION(PLASMA_VERSION)
#endif
// clang-format on

#endif
