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

#include "constants.h"
#include "pluginitem.h"
#include "plugin.h"
#include "widgetplugin.h"
#include "dockcontextmenu.h"

#include <xdgactivation.h>

#include <QHBoxLayout>
#include <QMouseEvent>
#include <QMenu>
#include <QPainter>
#include <QProcess>
#include <QProcessEnvironment>

const static QString DockQuickPlugins = "Dock_Quick_Plugins";

PluginItem::PluginItem(PluginsItemInterface *pluginItemInterface, const QString &itemKey, QWidget *parent)
    : QWidget(parent)
    , m_itemKey(itemKey)
    , m_pluginsItemInterface(pluginItemInterface)
    , m_menu(new DockContextMenu(nullptr))
    , m_tooltipTimer(new QTimer(this))
    , m_tipsWidget(nullptr)
{
    m_tooltipTimer->setSingleShot(true);
    m_tooltipTimer->setInterval(200);

    if (m_dbusProxy.isNull())
        m_dbusProxy.reset(new DockDBusProxy);

    connect(m_menu, &QMenu::triggered, this, [this](QAction *action){
        QString actionStr = action->data().toString();
        if (actionStr.startsWith(Dock::SHUTDOWN_MENU_FLAG)) {
            handleShutDownMenu(actionStr);
            return;
        }

        if (actionStr == Dock::dockMenuItemId || actionStr == Dock::unDockMenuItemId) {
            m_dbusProxy->setItemOnDock(DockQuickPlugins, m_itemKey, actionStr == Dock::dockMenuItemId);
        } else {
            const QString menuId = action->data().toString();
            const bool checked = action->isCheckable() ? action->isChecked() : true;
            auto *activation = new tray::XdgActivation(this);
            connect(activation, &tray::XdgActivation::tokenReady, this, [this, activation, menuId, checked](const QString &token) {
                if (!token.isEmpty())
                    qputenv("XDG_ACTIVATION_TOKEN", token.toUtf8());
                m_pluginsItemInterface->invokedMenuItem(m_itemKey, menuId, checked);
                if (!token.isEmpty())
                    qunsetenv("XDG_ACTIVATION_TOKEN");
                activation->deleteLater();
            }, Qt::SingleShotConnection);
            activation->requestToken();
        }
    });
}

PluginItem::~PluginItem()
{
    if (m_menu) {
        delete m_menu;
        m_menu = nullptr;
    }
}

QWidget *PluginItem::itemPopupApplet()
{
    if (auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey)) {
        bool existed = panelPopupExisted();
        bool embed = embedPanelPopupExisted();

        if (existed) {
            Plugin::PluginPopup::remove(popup->windowHandle());
        }

        // hided, when clicked again
        if (existed && !embed) {
            return nullptr;
        }

        dock::WidgetPlugin::updateDockContainerState(m_pluginsItemInterface, true);

        popup->setParent(nullptr);
        popup->setAttribute(Qt::WA_TranslucentBackground);
        popup->winId();

        auto pluginPopup = Plugin::PluginPopup::get(popup->windowHandle());
        connect(pluginPopup, &Plugin::PluginPopup::eventGeometry, this, &PluginItem::updatePopupSize);

        pluginPopup->setPluginId(m_pluginsItemInterface->pluginName());
        pluginPopup->setItemKey(m_itemKey);
        pluginPopup->setPopupType(Plugin::PluginPopup::PopupTypePanel);
        return popup;
    }
    return nullptr;
}

QMenu *PluginItem::pluginContextMenu()
{
    if (!m_menu->actions().isEmpty()) {
        m_menu->clear();
    }

    initPluginMenu();
    if (m_pluginFlags & Dock::Attribute_CanSetting) {
        if (!m_unDockAction) {
            m_unDockAction = new QAction();
        }
        m_unDockAction->setEnabled(true);
        m_menu->addAction(m_unDockAction);

        m_unDockAction->setText(tr("Remove from dock"));
        m_unDockAction->setData(Dock::unDockMenuItemId);
    }

    qDebug() << "mouseRightButtonClicked:" << m_itemKey << m_menu->actions().size();

    if (m_menu->isEmpty())
        return nullptr;

    m_menu->setAttribute(Qt::WA_TranslucentBackground, true);
    // FIXME: qt5integration drawMenuItemBackground will draw a background event is's transparent
    auto pa = this->palette();
    pa.setColor(QPalette::ColorRole::Window, Qt::transparent);
    m_menu->setPalette(pa);
    m_menu->winId();

    auto pluginPopup = Plugin::PluginPopup::get(m_menu->windowHandle());
    pluginPopup->setPluginId(m_pluginsItemInterface->pluginName());
    pluginPopup->setItemKey(m_itemKey);
    pluginPopup->setPopupType(Plugin::PluginPopup::PopupTypeMenu);
    m_menu->setFixedSize(m_menu->sizeHint());
    return m_menu;
}

void PluginItem::mousePressEvent(QMouseEvent *e)
{
    closeToolTip();
    QWidget::mousePressEvent(e);
}

void PluginItem::mouseReleaseEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton) {
        if (executeCommand())
            return;

        if (auto popup = itemPopupApplet()) {
            if (auto pluginPopup = Plugin::PluginPopup::get(popup->windowHandle())) {
                auto plugin = Plugin::EmbedPlugin::get(window()->windowHandle());
                auto geometry = plugin->pluginPos();
                const auto offset = QPoint(0, 0);
                pluginPopup->setX(geometry.x() + offset.x());
                pluginPopup->setY(geometry.y() + offset.y());
                popup->show();
            }
        }
    } else if (e->button() == Qt::RightButton) {
        if (auto menu = pluginContextMenu()) {
            if (auto pluginPopup = Plugin::PluginPopup::get(menu->windowHandle())) {
                auto plugin = Plugin::EmbedPlugin::get(windowHandle());
                auto geometry = plugin->pluginPos();
                const auto offset = e->pos();
                pluginPopup->setX(geometry.x() + offset.x());
                pluginPopup->setY(geometry.y() + offset.y());
                menu->show();
            }
        }
    }
    QWidget::mouseReleaseEvent(e);
}

void PluginItem::enterEvent(QEnterEvent *event)
{
    // panel popup existed, not show tooltip
    if (panelPopupExisted()) {
        return QWidget::enterEvent(event);
    }

    if (auto toolTip = pluginTooltip()) {
        if (auto pluginPopup = Plugin::PluginPopup::get(toolTip->windowHandle())) {
            auto plugin = Plugin::EmbedPlugin::get(windowHandle());
            auto geometry = plugin->pluginPos();
            const auto offset = QPoint(width() / 2, height() / 2);
            pluginPopup->setX(geometry.x() + offset.x());
            pluginPopup->setY(geometry.y() + offset.y());

            connect(m_tooltipTimer, &QTimer::timeout, toolTip, [this, toolTip] {
                toolTip->show();
                m_tooltipTimer->disconnect(toolTip);
            });
            m_tooltipTimer->start();
        }
    }

    QWidget::enterEvent(event);
}

void PluginItem::moveEvent(QMoveEvent *e)
{
    if (auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey); popup && popup->isVisible()) {
        if (auto pluginPopup = Plugin::PluginPopup::get(popup->windowHandle())) {
            auto plugin = Plugin::EmbedPlugin::get(windowHandle());
            auto geometry = plugin->pluginPos();
            const auto offset = QPoint(0, 0);
            pluginPopup->setX(geometry.x() + offset.x());
            pluginPopup->setY(geometry.y() + offset.y());
        }
    }
    QWidget::moveEvent(e);
}

void PluginItem::leaveEvent(QEvent *event)
{
    Q_UNUSED(event)
    closeToolTip();
}

// TODO: The reason we add this is because on openSUSE we can see render residue for those
//       dock plugins. This might also happen on later Qt releases (i.e. larger than the
//       version that deepin 25 currently uses, which is Qt 6.8.0) See the following PR
//       for related screenshot: https://github.com/linuxdeepin/dde-tray-loader/pull/383
void PluginItem::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setCompositionMode(QPainter::CompositionMode_Source);
    painter.fillRect(rect(), Qt::transparent);
    QWidget::paintEvent(event);
}

QWidget* PluginItem::centralWidget()
{
    const int trayItemWidth = 16;
    const int trayItemHeight = 16;

    auto trayItemWidget = m_pluginsItemInterface->itemWidget(m_itemKey);
    auto policy = m_pluginsItemInterface->pluginSizePolicy();
    if (policy == PluginsItemInterface::System) {
        trayItemWidget->setFixedSize(trayItemWidth, trayItemHeight);
    } else {
        auto size = trayItemWidget->sizeHint();
        if (size.width() > 0 && size.height() > 0) {
            trayItemWidget->setFixedSize(trayItemWidget->sizeHint());
        }
    }

    return trayItemWidget;
}

PluginsItemInterface * PluginItem::pluginsItemInterface()
{
    return m_pluginsItemInterface;
}

void PluginItem::updateItemWidgetSize(const QSize &size)
{
    centralWidget()->setFixedSize(size);
    update();
}

int PluginItem::pluginFlags() const
{
    return m_pluginFlags;
}

void PluginItem::setPluginFlags(int flags)
{
    m_pluginFlags = flags;
}

void PluginItem::updatePopupSize(const QRect &rect)
{
    if (auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey)) {
        popup->setFixedSize(rect.size());
        popup->update();
    }
}

void PluginItem::handleShutDownMenu(const QString &menuId)
{
    if (menuId.size() <= Dock::SHUTDOWN_MENU_FLAG.size())
        return;

    QString type = menuId.right(menuId.size() - Dock::SHUTDOWN_MENU_FLAG.size());
    Q_EMIT sigRequestShutdown(type);
}

void PluginItem::init()
{
    winId();
    setAttribute(Qt::WA_TranslucentBackground);

    auto hLayout = new QHBoxLayout;
    hLayout->addWidget(centralWidget());
    hLayout->setContentsMargins(0, 0, 0, 0);
    hLayout->setSpacing(0);
    hLayout->setSizeConstraint(QLayout::SetFixedSize);

    setLayout(hLayout);
}

void PluginItem::initPluginMenu()
{
    const QString menuJson = m_pluginsItemInterface->itemContextMenu(m_itemKey);
    if (menuJson.isEmpty()) {
        qWarning() << "itemContextMenu is empty!";
        return;
    }

    QJsonDocument jsonDocument = QJsonDocument::fromJson(menuJson.toLocal8Bit().data());
    if (jsonDocument.isNull()) {
        qWarning() << "jsonDocument is null!";
        return;
    }

    QJsonObject jsonMenu = jsonDocument.object();

    QJsonArray jsonMenuItems = jsonMenu.value("items").toArray();
    for (auto item : jsonMenuItems) {
        QJsonObject itemObj = item.toObject();
        QAction *action = new QAction(itemObj.value("itemText").toString());
        action->setCheckable(itemObj.value("isCheckable").toBool());
        action->setChecked(itemObj.value("checked").toBool());
        action->setData(itemObj.value("itemId").toString());
        action->setEnabled(itemObj.value("isActive").toBool());
        auto showReminder = itemObj.value("showReminder").toBool();
        action->setProperty("showReminder", showReminder);
        m_menu->addAction(action);
    }
}

QWidget *PluginItem::pluginTooltip()
{
    auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey);
    if (popup && popup->isVisible())
        popup->windowHandle()->hide();

    return itemTooltip(m_itemKey);
}

QWidget * PluginItem::itemTooltip(const QString &itemKey)
{
    if (!m_tipsWidget) {
        auto toolTip = m_pluginsItemInterface->itemTipsWidget(itemKey);
        if (!toolTip) {
            qDebug() << "no tooltip";
            return nullptr;
        }
        m_tipsWidget = new QWidget();
        QVBoxLayout *layout = new QVBoxLayout(m_tipsWidget);
        // add content margin, tooltip popup do not need to set padding
        layout->setContentsMargins(8, 4, 8, 4);
        layout->addWidget(toolTip);
        layout->setSizeConstraint(QLayout::SetFixedSize);
        toolTip->setVisible(true);
    } else {
        // can update tooltip content
        m_pluginsItemInterface->itemTipsWidget(itemKey);
    }

    m_tipsWidget->setParent(nullptr);
    m_tipsWidget->setAttribute(Qt::WA_TranslucentBackground);
    m_tipsWidget->winId();

    auto pluginPopup = Plugin::PluginPopup::get(m_tipsWidget->windowHandle());
    pluginPopup->setPluginId(m_pluginsItemInterface->pluginName());
    pluginPopup->setItemKey(itemKey);
    pluginPopup->setPopupType(Plugin::PluginPopup::PopupTypeTooltip);
    return m_tipsWidget;
}

bool PluginItem::executeCommand()
{
    const QString command = m_pluginsItemInterface->itemCommand(m_itemKey);
    if (!command.isEmpty()) {
        qInfo() << "command: " << command;

        if (command == Dock::REQUEST_SHUTDOWN) {
            emit sigRequestShutdown(QString());
            return true;
        }

        QStringList commandList = command.split(" ");
        QString program = commandList.takeFirst(); // 剩下是参数

        auto *activation = new tray::XdgActivation(this);
        connect(activation, &tray::XdgActivation::tokenReady, this, [activation, program, commandList](const QString &token) {
            QProcess process;
            QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
            if (!token.isEmpty())
                env.insert("XDG_ACTIVATION_TOKEN", token);
            process.setProcessEnvironment(env);
            process.startDetached(program, commandList);
            activation->deleteLater();
        }, Qt::SingleShotConnection);
        activation->requestToken();
        return true;
    }
    return false;
}

bool PluginItem::panelPopupExisted() const
{
    if (auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey)) {
        if (Plugin::PluginPopup::contains(popup->windowHandle())) {
            return true;
        }
    }

    return false;
}

bool PluginItem::embedPanelPopupExisted() const
{
    if (auto popup = m_pluginsItemInterface->itemPopupApplet(m_itemKey)) {
        if(auto pluginPopup = Plugin::PluginPopup::getWithoutCreating(popup->windowHandle())) {
            return pluginPopup->popupType() == Plugin::PluginPopup::PopupTypeEmbed;
        }
    }
    return false;
}

void PluginItem::closeToolTip()
{
    if (m_tooltipTimer->isActive()) {
        m_tooltipTimer->stop();
    }

    if (m_tipsWidget)
        m_tipsWidget->hide();
}

void PluginItem::updatePluginContentMargin(int spacing)
{
    if (spacing > 0) {
        m_spacing = spacing;
        // 时间日期插件的margin需要特殊处理，保证hover时和任务栏边框有一定间隔
        if (pluginId() == "datetime") {
            const auto position = qApp->property(PROP_POSITION).value<Dock::Position>();
            if (position == Dock::Left || position == Dock::Right) {
                setContentsMargins(1, spacing, 1, spacing);
            } else {
                setContentsMargins(spacing, 2, spacing, 2);
            }
        } else {
            setContentsMargins(spacing, spacing, spacing, spacing);
        }
        setFixedSize(sizeHint());
    }
}

bool PluginItem::isForceUndock(int pluginFlag)
{
    if (pluginFlag & Attribute_ForceUnDock) {
        return true;
    }

    return false;
}
