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

#include "shortcutlogging.h"

#include "treelandshortcutwrapper.h"

#include <QDebug>
#include <QEventLoop>
#include <QGuiApplication>
#include <QTimer>
#include <QtCore/qnativeinterface.h>

extern "C" {
#include <wayland-client-core.h>
}

namespace {
// commit() only queues the bind/unbind/commit messages into libwayland's
// internal outgoing buffer. With no other wayland traffic on this client
// (dde-services has no window), the buffer can sit unflushed for minutes
// until some D-Bus call wakes the Qt event loop. Force the flush so the
// compositor sees the change as soon as the caller intended.
inline void flushWaylandDisplay()
{
    if (auto *wlApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>()) {
        wl_display_flush(wlApp->display());
    }
}

const char *bindErrorName(uint32_t error)
{
    switch (error) {
    case 1:
        return "name_conflict";
    case 2:
        return "duplicate_binding";
    case 3:
        return "invalid_argument";
    case 4:
        return "internal_error";
    default:
        return "unknown";
    }
}
}  // namespace

TreelandShortcutWrapper::TreelandShortcutWrapper(QObject *parent)
    : QWaylandClientExtensionTemplate<TreelandShortcutWrapper>(2)
    , QtWayland::treeland_shortcut_manager_v2()
{
    setParent(parent);

    connect(this, &TreelandShortcutWrapper::activeChanged, this, [this]() {
        if (isActive()) {
            qCDebug(logShortcut) << "TreelandShortcutManager protocol is now active, acquiring...";
            acquire();
            m_boundObject = object();  // Save bound object for session recovery check
            emit ready();  // Emit signal directly since signal is connected before init() is called
        } else {
            qCWarning(logShortcut) << "TreelandShortcutManager protocol is now inactive!";
            m_boundObject = nullptr;
            emit protocolInactive();
        }
    });

    // Don't call initialize() in constructor; let external code call init() to trigger it
}

void TreelandShortcutWrapper::initProtocol()
{
    initialize();
}

TreelandShortcutWrapper::~TreelandShortcutWrapper()
{
    if (isActive()) {
        destroy();
    }
}

bool TreelandShortcutWrapper::bindKey(const QString &name, const QString &key, uint32_t flags, int action)
{
    if (!isActive()) {
        return false;
    }

    bind_key(name, key, flags, action);
    return true;
}

bool TreelandShortcutWrapper::bindSwipeGesture(const QString &name, int finger, int direction, int action)
{
    if (!isActive()) {
        return false;
    }

    bind_swipe_gesture(name, finger, direction, action);
    return true;
}

bool TreelandShortcutWrapper::bindHoldGesture(const QString &name, int finger, int action)
{
    if (!isActive()) {
        return false;
    }

    bind_hold_gesture(name, finger, action);
    return true;
}

bool TreelandShortcutWrapper::unbind(const QString &name)
{
    if (!isActive()) {
        return false;
    }

    // Check if object changed (session recovery scenario)
    if (object() != m_boundObject) {
        qCWarning(logShortcut) << "TreelandShortcutWrapper::unbind: object changed, skip unbind for" << name;
        return false;
    }

    QtWayland::treeland_shortcut_manager_v2::unbind(name);
    return true;
}

void TreelandShortcutWrapper::commit()
{
    if (!isActive()) {
        return;
    }

    QtWayland::treeland_shortcut_manager_v2::commit();
    flushWaylandDisplay();
}

void TreelandShortcutWrapper::commitDeferred()
{
    // Subsequent calls in this tick are folded into the queued one —
    // their bind/unbind ops already sit in the same wire-protocol queue.
    if (m_commitPending) {
        return;
    }
    m_commitPending = true;
    QTimer::singleShot(0, this, [this]() {
        m_commitPending = false;
        if (!isActive()) {
            return;
        }
        QtWayland::treeland_shortcut_manager_v2::commit();
        flushWaylandDisplay();
    });
}

bool TreelandShortcutWrapper::commitAndWait(int timeoutMs)
{
    if (!isActive()) {
        return false;
    }

    QEventLoop loop;
    bool success = false;
    bool responded = false;

    connect(this, &TreelandShortcutWrapper::commitStatus, &loop, [&](bool status) {
        loop.quit();
        success = status;
        responded = true;
        qCDebug(logShortcut) << "TreelandShortcutWrapper::commitAndWait response:" << status;
    }, Qt::SingleShotConnection);

    QTimer::singleShot(timeoutMs, &loop, [&]() {
        if (!responded) {
            qCWarning(logShortcut) << "TreelandShortcutWrapper::commitAndWait timeout after" << timeoutMs << "ms";
            loop.quit();
        }
    });

    QtWayland::treeland_shortcut_manager_v2::commit();
    flushWaylandDisplay();
    loop.exec();
    return success;
}

void TreelandShortcutWrapper::treeland_shortcut_manager_v2_activated(const QString &name, uint32_t flags)
{
    qCDebug(logShortcut) << "Treeland Shortcut Manager Activated:" << name << "flags:" << flags;
    emit activated(name, flags);
}

void TreelandShortcutWrapper::treeland_shortcut_manager_v2_commit_success()
{
    emit commitStatus(true);
}

void TreelandShortcutWrapper::treeland_shortcut_manager_v2_commit_failure(const QString &name, uint32_t error)
{
    qCWarning(logShortcut) << "Treeland Shortcut Manager Commit Failed:" << name
               << "Error:" << error << bindErrorName(error);
    emit commitStatus(false);
}
