// Copyright (C) 2025-2026 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "shortcutrunner.h"

#include "core/qmlengine.h"
#include "core/rootsurfacecontainer.h"
#include "interfaces/multitaskviewinterface.h"
#include "modules/window-management/windowmanagementinterfacev1.h"
#include "output/output.h"
#include "seat/helper.h"
#include "shortcutcontroller.h"
#include "surface/quicktile.h"
#include "surface/surfacewrapper.h"
#include "treelandconfig.hpp"
#include "utils/fpsdisplaymanager.h"
#include "workspace/workspace.h"
#include "workspaceanimationcontroller.h"
#include "woutputrenderwindow.h"

ShortcutRunner::ShortcutRunner(QObject *parent)
    : QObject(parent)
{
    auto *helper = Helper::instance();
    connect(helper, &Helper::modifierKeyReleased, this, &ShortcutRunner::onModifierReleased);

    m_quickSwitchTimer = new QTimer(this);
    m_quickSwitchTimer->setSingleShot(true);
    connect(m_quickSwitchTimer, &QTimer::timeout, this, &ShortcutRunner::onQuickSwitchTimeout);
}

void ShortcutRunner::onActionTrigger(ShortcutAction action, const QString &name, bool isGesture, ShortcutController::KeyFlags keyFlags)
{
    Q_UNUSED(isGesture)
    auto *helper = Helper::instance();

    if (helper->currentMode() == Helper::CurrentMode::LockScreen) {
        return;
    }

    m_currentAction = action;
    switch (action) {
    case ShortcutAction::Notify:
        helper->m_shortcutManager->sendActivated(name, keyFlags);
        break;
    case ShortcutAction::Workspace1:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(0);
        break;
    case ShortcutAction::Workspace2:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(1);
        break;
    case ShortcutAction::Workspace3:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(2);
        break;
    case ShortcutAction::Workspace4:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(3);
        break;
    case ShortcutAction::Workspace5:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(4);
        break;
    case ShortcutAction::Workspace6:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchTo(5);
        break;
    case ShortcutAction::PrevWorkspace:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchToPrev();
        break;
    case ShortcutAction::NextWorkspace:
        helper->restoreFromShowDesktop();
        helper->workspace()->switchToNext();
        break;
    case ShortcutAction::ShowDesktop:
        if (helper->currentMode() == Helper::CurrentMode::Multitaskview) {
            break;
        }
        if (helper->m_showDesktop == WindowManagementInterfaceV1::DesktopState::Normal)
            helper->m_windowManagementInterfaceV1->setDesktopState(WindowManagementInterfaceV1::DesktopState::Show);
        else if (helper->m_showDesktop == WindowManagementInterfaceV1::DesktopState::Show)
            helper->m_windowManagementInterfaceV1->setDesktopState(WindowManagementInterfaceV1::DesktopState::Normal);
        break;
    case ShortcutAction::Maximize: {
        auto surface = helper->activatedSurface();
        if (surface && surface->isMaximizable()) {
            surface->maximize();
        }
        break;
    }
    case ShortcutAction::CancelMaximize: {
        auto surface = helper->activatedSurface();
        if (surface) {
            surface->unmaximize();
        }
        break;
    }
    case ShortcutAction::MoveWindow: {
        auto surface = helper->activatedSurface();
        if (surface) {
            Q_EMIT surface->moveRequested();
        }
        break;
    }
    case ShortcutAction::TileLeft:
    case ShortcutAction::TileRight: {
        auto surface = helper->activatedSurface();
        if (!surface)
            break;
        auto *out = surface->ownsOutput();
        if (!out)
            break;
        const auto mode =
            (action == ShortcutAction::TileLeft) ? QuickTile::Mode::Left : QuickTile::Mode::Right;
        QuickTile::apply(surface, mode, out);
        break;
    }
    case ShortcutAction::CloseWindow: {
        auto surface = helper->activatedSurface();
        if (surface) {
            surface->closeSurface();
        }
        break;
    }
    case ShortcutAction::ShowWindowMenu:
        if (auto surface = helper->activatedSurface()) {
            Q_EMIT surface->windowMenuRequested({ 0, 0 });
        }
        break;
    case ShortcutAction::OpenMultiTaskView:
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview()
            || helper->currentMode() == Helper::CurrentMode::Multitaskview) {
            break;
        }
        helper->m_multitaskView->setStatus(IMultitaskView::Exited);
        helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::ShortcutKey);
        break;
    case ShortcutAction::CloseMultiTaskView:
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview()
            || helper->currentMode() == Helper::CurrentMode::Normal) {
            break;
        }
        helper->m_multitaskView->setStatus(IMultitaskView::Active);
        helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::ShortcutKey);
        break;
    case ShortcutAction::ToggleMultitaskView:
        if (helper->isNormalOrMultitaskview()) {
            helper->restoreFromShowDesktop();
            if (helper->m_multitaskView) {
                helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::ShortcutKey);
            }
        }
        break;
    case ShortcutAction::ToggleFpsDisplay:
        helper->toggleFpsDisplay();
        break;
    case ShortcutAction::Lockscreen:
        if (helper->isNormalOrMultitaskview())
            helper->showLockScreen();
        break;
    case ShortcutAction::ShutdownMenu:
        helper->showShutdownMenu();
        break;
    case ShortcutAction::Quit:
        Q_EMIT helper->requestQuit();
        break;
    case ShortcutAction::TaskSwitchNext:
    case ShortcutAction::TaskSwitchPrev:
    case ShortcutAction::TaskSwitchSameAppNext:
    case ShortcutAction::TaskSwitchSameAppPrev: {
        if (keyFlags.testFlag(ShortcutController::Repeat)
            && helper->currentMode() == Helper::CurrentMode::Normal
            && !m_quickSwitchPending) {
            return;
        }

        const bool isSameApp = (action == ShortcutAction::TaskSwitchSameAppNext || action == ShortcutAction::TaskSwitchSameAppPrev);
        const bool isPrev = (action == ShortcutAction::TaskSwitchPrev || action == ShortcutAction::TaskSwitchSameAppPrev);
        taskswitchAction(keyFlags.testFlag(ShortcutController::Repeat), isSameApp, isPrev);
        break;
    }
    default:
        break;
    }
}

void ShortcutRunner::onActionProgress(ShortcutAction action, qreal progress, const QString &name)
{
    Q_UNUSED(name);
    switch (action) {
    case ShortcutAction::PrevWorkspace:
        updateWorkspaceSwipe(-progress);
        break;
    case ShortcutAction::NextWorkspace:
        updateWorkspaceSwipe(progress);
        break;
    case ShortcutAction::OpenMultiTaskView:
    {
        auto helper = Helper::instance();
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview())
            break;
        if (helper->currentMode() == Helper::CurrentMode::Normal
            && qFuzzyIsNull(helper->m_multitaskView->partialFactor())) {
            helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::Gesture);
        }
        if (helper->currentMode() == Helper::CurrentMode::Multitaskview) {
            break;
        }
        helper->m_multitaskView->updatePartialFactor(progress);
        break;
    }
    case ShortcutAction::CloseMultiTaskView:
    {
        auto helper = Helper::instance();
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview())
            break;
        if (helper->currentMode() != Helper::CurrentMode::Multitaskview) {
            break;
        }
        helper->m_multitaskView->updatePartialFactor(1 - progress);
        break;
    }
    default:
        break;
    }
}

void ShortcutRunner::onActionFinish(ShortcutAction action, const QString &name, bool isTriggered)
{
    Q_UNUSED(name);
    switch (action) {
    case ShortcutAction::PrevWorkspace:
    case ShortcutAction::NextWorkspace:
        finishWorkspaceSwipe();
        break;
    case ShortcutAction::OpenMultiTaskView:
    {
        auto helper = Helper::instance();
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview())
            break;
        const bool triggered = helper->m_multitaskView->partialFactor() > 0.5;
        helper->m_multitaskView->setStatus(triggered ? IMultitaskView::Active
                                                     : IMultitaskView::Exited);
        helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::Gesture);
        helper->m_multitaskView->commitGesture(triggered);
        break;
    }
    case ShortcutAction::CloseMultiTaskView:
    {
        auto helper = Helper::instance();
        if (!helper->m_multitaskView || !helper->isNormalOrMultitaskview())
            break;
        const bool triggered = (1.0 - helper->m_multitaskView->partialFactor()) > 0.5;
        if (qFuzzyCompare(helper->m_multitaskView->partialFactor(), 0.0)
            && helper->currentMode() != Helper::CurrentMode::Multitaskview)
            break;
        helper->m_multitaskView->setStatus(triggered ? IMultitaskView::Exited
                                                     : IMultitaskView::Active);
        helper->m_multitaskView->toggleMultitaskView(IMultitaskView::ActiveReason::Gesture);
        helper->m_multitaskView->commitGesture(!triggered);
        break;
    }
    default:
        if (isTriggered) {
            onActionTrigger(action, name, true, ShortcutController::KeyFlag::None);
        }
        break;
    }
}

void ShortcutRunner::updateWorkspaceSwipe(qreal cb)
{
    if (qFuzzyCompare(cb, m_desktopOffset))
        return;

    Workspace *workspace = Helper::instance()->workspace();
    Q_ASSERT(workspace);

    WorkspaceAnimationController *controller = workspace->animationController();
    Q_ASSERT(controller);

    m_desktopOffset = cb;

    if (!m_slideEnable) {
        m_slideEnable = true;
        m_fromId = workspace->currentIndex();
        controller->slideNormal(m_fromId);
        workspace->createSwitcher();
        controller->setRunning(true);
    }

    controller->startGestureSlide(
        cb,
        isWorkspaceBounce(cb, workspace->currentIndex(), workspace->count()));
}

void ShortcutRunner::finishWorkspaceSwipe()
{
    if (!m_slideEnable)
        return;
    m_slideEnable = false;
    Workspace *workspace = Helper::instance()->workspace();
    int currentIdx = workspace->currentIndex();
    int wsCount = workspace->count();

    // determine target based on offset threshold, but never cross boundary
    m_toId = currentIdx;
    if (m_desktopOffset > 0.3) {
        if (!isWorkspaceBounce(m_desktopOffset, currentIdx, wsCount))
            m_toId = currentIdx + 1;
    } else if (m_desktopOffset <= -0.3) {
        if (!isWorkspaceBounce(m_desktopOffset, currentIdx, wsCount))
            m_toId = currentIdx - 1;
    }

    // final safety bounds check
    m_toId = qBound(0, m_toId, wsCount - 1);

    auto controller = workspace->animationController();
    controller->slideRunning(m_toId);
    controller->startSlideAnimation();
    workspace->switchTo(m_toId, false);
}
void ShortcutRunner::taskswitchAction(bool isRepeat, bool isSameApp, bool isPrev)
{
    auto *helper = Helper::instance();
    const auto modeBefore = helper->currentMode();
    if (modeBefore != Helper::CurrentMode::Normal && modeBefore != Helper::CurrentMode::WindowSwitch)
        return;

    if (!isRepeat && !isSameApp && modeBefore == Helper::CurrentMode::Normal) {
        auto *current = helper->workspace()->current();
        if (current) {
            auto nextSurface = current->findNextActivedSurface();
            if (nextSurface)
                helper->forceActivateSurface(nextSurface, Qt::TabFocusReason);
        }
        m_quickSwitchTimer->start(helper->globalConfig()->quickSwitchTimeout());
        m_quickSwitchPending = true;
        return;
    }

    if (helper->m_taskSwitch.isNull()) {
        auto contentItem = helper->window()->contentItem();
        auto output = helper->rootSurfaceContainer()->cursorOutput();
        if (!output) {
            output = helper->rootSurfaceContainer()->primaryOutput();
        }
        helper->m_taskSwitch = helper->qmlEngine()->createTaskSwitcher(output, contentItem);
        helper->restoreFromShowDesktop();
        QObject::connect(helper->m_taskSwitch, SIGNAL(switchOnChanged()), helper, SLOT(deleteTaskSwitch()));
        helper->m_taskSwitch->setZ(RootSurfaceContainer::OverlayZOrder);
    }

    if (isRepeat) {
        m_taskAltCount++;
    } else {
        m_taskAltCount = 3;
    }

    if (m_taskAltCount < 3)
        return;

    m_taskAltCount = 0;
    helper->setCurrentMode(Helper::CurrentMode::WindowSwitch);

    if (m_quickSwitchPending) {
        m_quickSwitchTimer->stop();
        m_quickSwitchPending = false;
    }

    if (isSameApp) {
        QMetaObject::invokeMethod(helper->m_taskSwitch, isPrev ? "previousSameApp" : "nextSameApp");
    } else {
        auto filter = helper->workspace()->currentFilter();
        filter->setFilterAppId({});
        QMetaObject::invokeMethod(helper->m_taskSwitch, isPrev ? "previous" : "next");
    }
}

void ShortcutRunner::onQuickSwitchTimeout()
{
    auto *helper = Helper::instance();
    if (helper->currentMode() != Helper::CurrentMode::Normal) {
        m_quickSwitchPending = false;
        return;
    }

    if (helper->m_taskSwitch.isNull()) {
        auto contentItem = helper->window()->contentItem();
        auto output = helper->rootSurfaceContainer()->cursorOutput();
        if (!output) {
            output = helper->rootSurfaceContainer()->primaryOutput();
        }
        helper->m_taskSwitch = helper->qmlEngine()->createTaskSwitcher(output, contentItem);
        helper->restoreFromShowDesktop();
        QObject::connect(helper->m_taskSwitch,
                         SIGNAL(switchOnChanged()),
                         helper,
                         SLOT(deleteTaskSwitch()));
        helper->m_taskSwitch->setZ(RootSurfaceContainer::OverlayZOrder);
    }
    helper->setCurrentMode(Helper::CurrentMode::WindowSwitch);

    QMetaObject::invokeMethod(helper->m_taskSwitch, "show");
}

void ShortcutRunner::onModifierReleased(QKeyEvent *event)
{
    auto *helper = Helper::instance();

    const QList<ShortcutAction> taskSwitchActions = {
        ShortcutAction::TaskSwitchNext,
        ShortcutAction::TaskSwitchPrev,
        ShortcutAction::TaskSwitchSameAppNext,
        ShortcutAction::TaskSwitchSameAppPrev,
    };

    if (taskSwitchActions.contains(m_currentAction)) {
        auto *controller = helper->m_shortcutManager->controller();
        const auto currentModifiers = controller->modifierForAction(m_currentAction);

        constexpr auto supportedModifiers =
            Qt::AltModifier | Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier;

        auto keyCombination = QKeyCombination(Qt::NoModifier,static_cast<Qt::Key>(event->key()));
        const auto releasedModifier = ShortcutController::normalizeKeyCombination(keyCombination).keyboardModifiers() & supportedModifiers;

        if (!(currentModifiers & releasedModifier))
            return;

        auto remainingModifiers = event->modifiers() & supportedModifiers;
        remainingModifiers &= ~releasedModifier;

        const bool hasActiveTaskSwitchModifiers =
            std::any_of(taskSwitchActions.cbegin(), taskSwitchActions.cend(), [
                controller,
                currentModifiers,
                remainingModifiers](auto action) {
            const auto modifiers = controller->modifierForAction(action);
            if (modifiers == Qt::NoModifier)
                return false;
            if ((modifiers & currentModifiers) != modifiers)
                return false;
            return (remainingModifiers & modifiers) == modifiers;
        });
        if (hasActiveTaskSwitchModifiers)
            return;

        if (m_quickSwitchPending && helper->currentMode() == Helper::CurrentMode::Normal) {
            m_quickSwitchTimer->stop();
            m_quickSwitchPending = false;
            return;
        }

        if (helper->currentMode() == Helper::CurrentMode::WindowSwitch && helper->m_taskSwitch) {
            auto filter = helper->workspace()->currentFilter();
            filter->setFilterAppId("");
            helper->setCurrentMode(Helper::CurrentMode::Normal);
            m_quickSwitchPending = false;
            QMetaObject::invokeMethod(helper->m_taskSwitch, "exit");
        }
    }
}
