// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include "shortcutconfig.h"
#include "crosschannelactivationguard.h"

#include <QElapsedTimer>
#include <QObject>
#include <QVariant>
#include <QDBusContext>
#include <QDBusArgument>
#include <QDBusMetaType>
#include <QSet>


class ConfigLoader;
class ActionExecutor;
class AbstractKeyHandler;
class SessionActiveMonitor;
class SpecialKeyHandler;
class TranslationManager;
class X11GestureActionExecutor;
class X11NumLockStateController;

struct ShortcutInfo {
    QString id;
    QString displayName;
    QString category;
    QStringList hotkeys;
    QString localLanguageName;
    QString localLanguageCategory;
    bool isCustom = false;
    bool modifiable = false;
};
Q_DECLARE_METATYPE(ShortcutInfo)

// Category metadata for the ListCategories() interface.
struct CategoryInfo {
    QString key;                    // Logical category key
    QString displayName;            // Resolved display text
    int order = 0;                  // Display order (lower = earlier)
    bool isCustom = false;          // True for the user-editable category
};
Q_DECLARE_METATYPE(CategoryInfo)
Q_DECLARE_METATYPE(QList<CategoryInfo>)

class KeybindingManager : public QObject, protected QDBusContext
{
    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.deepin.dde.Keybinding1")
    
    // Lock key state properties (0=off, 1=on)
    Q_PROPERTY(uint NumLockState READ GetNumLockState WRITE SetNumLockState NOTIFY NumLockStateChanged)
    Q_PROPERTY(uint CapsLockState READ GetCapsLockState WRITE SetCapsLockState NOTIFY CapsLockStateChanged)

public:
    explicit KeybindingManager(ConfigLoader *loader, ActionExecutor *executor, 
                               TranslationManager *translationManager, 
                               AbstractKeyHandler *keyHandler,
                               SessionActiveMonitor *sessionMonitor,
                               X11GestureActionExecutor *x11ActionExecutor = nullptr,
                               QObject *parent = nullptr);
    ~KeybindingManager() override;

    /** Register all configured shortcuts in the current backend batch. */
    void registerAllShortcuts();

    /**
     * @brief Clear internal state (called when protocol disconnects)
     */
    void clearState();

public slots:
    // DBus Methods
    Q_SCRIPTABLE QList<ShortcutInfo> ListAllShortcuts();
    Q_SCRIPTABLE QList<ShortcutInfo> ListShortcutsByApp(const QString &appId);
    Q_SCRIPTABLE QList<ShortcutInfo> ListShortcutsByCategory(const QString &category);

    Q_SCRIPTABLE QList<CategoryInfo> ListCategories();

    Q_SCRIPTABLE ShortcutInfo GetShortcut(const QString &id);
    Q_SCRIPTABLE QString GetShortcutCommand(const QString &id);
    Q_SCRIPTABLE ShortcutInfo LookupConflictShortcut(const QString &hotkey);
    
    Q_SCRIPTABLE QList<ShortcutInfo> SearchShortcuts(const QString &keyword);
    Q_SCRIPTABLE bool ModifyHotkeys(const QString &id, const QStringList &newHotkeys);
    Q_SCRIPTABLE bool Disable(const QString &id);
    Q_SCRIPTABLE QString AddCustomShortcut(const QString &name, const QString &action, const QString &hotkey);
    Q_SCRIPTABLE QString AddCustomShortcutWithConflict(const QString &name, const QString &action,
                                                       const QString &hotkey,
                                                       const QString &expectedConflictId);
    Q_SCRIPTABLE bool ModifyCustomShortcut(const QString &id, const QString &name, const QString &action, const QString &hotkey);
    Q_SCRIPTABLE bool ModifyCustomShortcutWithConflict(const QString &id, const QString &name,
                                                       const QString &action, const QString &hotkey,
                                                       const QString &expectedConflictId);
    Q_SCRIPTABLE bool DeleteCustomShortcut(const QString &id);
    Q_SCRIPTABLE bool BeginCapture(uint timeoutMs = 30000);
    Q_SCRIPTABLE bool BeginCapture(quint64 captureId, uint timeoutMs);
    Q_SCRIPTABLE void EndCapture();

    // Atomically swap the hotkeys of two shortcuts in a single compositor commit.
    Q_SCRIPTABLE bool SwapHotkeys(const QString &id1, const QString &id2);
    // Remove newHotkey from conflictId and assign it to targetId in one commit.
    Q_SCRIPTABLE bool ReplaceHotkey(const QString &targetId, const QString &newHotkey, const QString &conflictId);
    Q_SCRIPTABLE void ReloadConfigs();
    Q_SCRIPTABLE void Reset();
    
    // Lock key state methods (0=off, 1=on)
    Q_SCRIPTABLE uint GetNumLockState() const;
    Q_SCRIPTABLE uint GetCapsLockState() const;
    Q_SCRIPTABLE void SetNumLockState(uint state);
    Q_SCRIPTABLE void SetCapsLockState(uint state);

signals:
    // DBus Signals
    Q_SCRIPTABLE void ShortcutChanged(const QString &id, const ShortcutInfo &info);
    Q_SCRIPTABLE void ShortcutActivated(const QString &id, const QStringList &triggerValue);
    Q_SCRIPTABLE void ShortcutRemoved(const QString &id);
    Q_SCRIPTABLE void KeyEvent(bool pressed, const QString &keystroke);
    Q_SCRIPTABLE void CaptureFinished(quint64 captureId, uint result, const QString &keystroke);
    
    // Lock key state signals (0=off, 1=on)
    Q_SCRIPTABLE void NumLockStateChanged(uint state);
    Q_SCRIPTABLE void CapsLockStateChanged(uint state);

private slots:
    void onKeyConfigAdded(const KeyConfig &config);
    void onKeyConfigChanged(const KeyConfig &config);
    void onConfigRemoved(const QString &id);
    void onKeyActivated(const QString &shortcutId);
    void onSpecialKeyActivated(const QString &shortcutId);
    void onCaptureKeyEvent(bool pressed, const QString &keystroke);
    void onCaptureResult(quint64 captureId, uint result, const QString &keystroke);
    void updateNumLockState(bool on);
    void updateCapsLockState(bool on);
    void onBackendKeymapAboutToChange();
    void onBackendKeymapChanged();
    ShortcutInfo toShortcutInfo(const KeyConfig &config);

private:
    enum class ShortcutActivationSource {
        Backend,
        SpecialKey,
    };

    enum class RollbackResult {
        Success,
        RebuildRequired,
    };

    bool registerShortcut(const KeyConfig &config, const QStringList &excludeIds = QStringList());
    void unregisterShortcut(const QString &id);
    QString lookupRuntimeConflict(const QString &hotkey, const QStringList &excludeIds) const;
    RollbackResult rollbackRegistration(const QString &id1, const QString &id2,
                                        KeyConfig &config1, KeyConfig &config2,
                                        const QStringList &hotkeys1, const QStringList &hotkeys2);
    void rebuildPersistedShortcutPair(const QString &id1, const QString &id2);
    QString localizedNoHotkeyText() const;
    bool isRuntimeCustomShortcut(const KeyConfig &config) const;
    bool canPersistShortcutHotkeys(const KeyConfig &config) const;
    int runtimeCustomShortcutCount() const;
    int nextCustomShortcutDisplayOrder() const;
    QString createCustomShortcutId() const;
    QString addCustomShortcut(const QString &name, const QString &action, const QString &hotkey,
                              const QString &expectedConflictId);
    bool modifyCustomShortcut(const QString &id, const QString &name, const QString &action,
                              const QString &hotkey, const QString &expectedConflictId);
    void updateCustomShortcutConfigFields(KeyConfig &config, const QString &displayName,
                                          const QStringList &commandArguments,
                                          const QString &normalizedHotkey) const;
    void activateShortcut(const QString &shortcutId, ShortcutActivationSource source);

    struct CustomShortcutChange {
        bool hasOldTarget = false;
        KeyConfig oldTarget;
        KeyConfig newTarget;

        bool hasConflict = false;
        KeyConfig oldConflict;
        KeyConfig newConflict;
    };
    class CustomShortcutTransaction;

    bool prepareConflictShortcutChange(const QString &hotkey, CustomShortcutChange &change,
                                       const QString &selfId,
                                       const QString &expectedConflictId);

    ConfigLoader *m_loader;
    AbstractKeyHandler *m_keyHandler;
    SpecialKeyHandler *m_specialKeyHandler;
    SessionActiveMonitor *m_sessionMonitor;
    ActionExecutor *m_executor;
    TranslationManager *m_translationManager;
    X11GestureActionExecutor *m_x11ActionExecutor;

    // id(shortcutId) -> KeyConfig
    QMap<QString, KeyConfig> m_keyConfigsMap;
    QSet<QString> m_activeShortcutIds;
    QSet<QString> m_resetInProgressIds;
    CrossChannelActivationGuard m_crossChannelActivationGuard;
    QElapsedTimer m_activationClock;
    uint m_lastNumLockState = 0;
    uint m_lastCapsLockState = 0;
    bool m_isWayland = false;
    X11NumLockStateController *m_numLockStateController = nullptr;
};

Q_DECLARE_METATYPE(QList<ShortcutInfo>)

inline QDBusArgument &operator<<(QDBusArgument &argument, const ShortcutInfo &info) {
    argument.beginStructure();
    argument << info.id << info.displayName << info.category << info.hotkeys
             << info.localLanguageName << info.localLanguageCategory << info.isCustom
             << info.modifiable;
    argument.endStructure();
    return argument;
}

inline const QDBusArgument &operator>>(const QDBusArgument &argument, ShortcutInfo &info) {
    argument.beginStructure();
    argument >> info.id >> info.displayName >> info.category >> info.hotkeys
             >> info.localLanguageName >> info.localLanguageCategory >> info.isCustom
             >> info.modifiable;
    argument.endStructure();
    return argument;
}

inline QDBusArgument &operator<<(QDBusArgument &argument, const CategoryInfo &info) {
    argument.beginStructure();
    argument << info.key << info.displayName << info.order << info.isCustom;
    argument.endStructure();
    return argument;
}

inline const QDBusArgument &operator>>(const QDBusArgument &argument, CategoryInfo &info) {
    argument.beginStructure();
    argument >> info.key >> info.displayName >> info.order >> info.isCustom;
    argument.endStructure();
    return argument;
}
