// SPDX-FileCopyrightText: 2025 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include "logging.h"
#include "xdgtoplevel.h"
#include "xdgdialogv1.h"
#include "ukuishellsurface.h"
#include "ukuishellsurfacev1.h"

#include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <QtWaylandClient/private/qwaylandsurface_p.h>
#include <QtWaylandClient/private/qwaylandscreen_p.h>

namespace QtWaylandClient
{

Toplevel::Toplevel(UkuiShellSurface *ukuiShellSurface)
    : xdg_toplevel(ukuiShellSurface->get_toplevel())
    , m_ukuiShellSurface(ukuiShellSurface)
{
    QWindow *window = ukuiShellSurface->window()->window();
    if (auto *decorationManager = ukuiShellSurface->shell()->decorationManager()) {
        if (!(window->flags() & Qt::FramelessWindowHint)) {
            m_decoration = decorationManager->createToplevelDecoration(object());
            if (window->flags() & Qt::WindowTransparentForInput && (QT_VERSION_MAJOR < 6 || (QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR < 6))) {
                qWarning() << "Seems like you are trying to make a window transparent for input,"
                              "but it is not supported on server side decorated windows. Please"
                              "set window flags to Qt::FramelessWindowHint to make it work.";
            }
        } else if ((window->flags() & Qt::WindowTransparentForInput) && (QT_VERSION_MAJOR < 6 || (QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR < 6))) {
            auto display = ukuiShellSurface->window()->display();
            auto input_region = display->createRegion(window->mask());
            m_ukuiShellSurface->window()->waylandSurface()->set_input_region(input_region);
            wl_region_destroy(input_region);
        }
    }
    requestWindowStates(window->windowStates());
    requestWindowFlags(window->flags());
    if (auto transientParent = m_ukuiShellSurface->window()->transientParent()) {
        if (auto parentSurface = qobject_cast<UkuiShellSurface *>(transientParent->shellSurface())) {
            set_parent(parentSurface->toplevel()->object());
            if (window->modality() != Qt::NonModal && m_ukuiShellSurface->shell()->xdgDialogWm()) {
                m_xdgDialog.reset(m_ukuiShellSurface->shell()->xdgDialogWm()->getDialog(object()));
                m_xdgDialog->set_modal();

                if (!window->dynamicPropertyNames().contains("ukui_surface_skip_taskbar"))
                    ukuiShellSurface->setSkipTaskbar(true);
                if (!window->dynamicPropertyNames().contains("ukui_surface_skip_switcher"))
                    ukuiShellSurface->setSkipSwitcher(true);
            }
        }
    }
}

Toplevel::Toplevel(UkuiShellSurfaceV1 *ukuiShellSurfaceV1)
    : xdg_toplevel(ukuiShellSurfaceV1->get_toplevel())
    , m_ukuiShellSurfaceV1(ukuiShellSurfaceV1)
{
    QWindow *window = ukuiShellSurfaceV1->window()->window();
    if (auto *decorationManager = ukuiShellSurfaceV1->shell()->decorationManager()) {
        if (!(window->flags() & Qt::FramelessWindowHint)) {
            m_decoration = decorationManager->createToplevelDecoration(object());
            if (window->flags() & Qt::WindowTransparentForInput && (QT_VERSION_MAJOR < 6 || (QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR < 6))) {
                qWarning() << "Seems like you are trying to make a window transparent for input,"
                              "but it is not supported on server side decorated windows. Please"
                              "set window flags to Qt::FramelessWindowHint to make it work.";
            }
        } else if ((window->flags() & Qt::WindowTransparentForInput) && (QT_VERSION_MAJOR < 6 || (QT_VERSION_MAJOR == 6 && QT_VERSION_MINOR < 6))) {
            auto display = ukuiShellSurfaceV1->window()->display();
            auto input_region = display->createRegion(window->mask());
            ukuiShellSurfaceV1->window()->waylandSurface()->set_input_region(input_region);
            wl_region_destroy(input_region);
        }
    }
    requestWindowStates(window->windowStates());
    requestWindowFlags(window->flags());
    if (auto transientParent = ukuiShellSurfaceV1->window()->transientParent()) {
        if (auto parentSurface = qobject_cast<UkuiShellSurfaceV1 *>(transientParent->shellSurface())) {
            set_parent(parentSurface->toplevel()->object());
            if (window->modality() != Qt::NonModal && ukuiShellSurfaceV1->shell()->xdgDialogWm()) {
                m_xdgDialog.reset(ukuiShellSurfaceV1->shell()->xdgDialogWm()->getDialog(object()));
                m_xdgDialog->set_modal();

                if (!window->dynamicPropertyNames().contains("ukui_surface_skip_taskbar"))
                    ukuiShellSurfaceV1->setSkipTaskbar(true);
                if (!window->dynamicPropertyNames().contains("ukui_surface_skip_switcher"))
                    ukuiShellSurfaceV1->setSkipSwitcher(true);
            }
        }
    }
}

Toplevel::~Toplevel()
{
    // The protocol spec requires that the decoration object is deleted before xdg_toplevel.
    delete m_decoration;
    m_decoration = nullptr;

    if (isInitialized())
        destroy();
}

void Toplevel::applyConfigure()
{
    if (m_ukuiShellSurfaceV1) {
        if (!(m_applied.states & (Qt::WindowMaximized | Qt::WindowFullScreen)))
            m_normalSize = m_ukuiShellSurfaceV1->window()->windowFrameGeometry().size();

        if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)
            && !m_ukuiShellSurfaceV1->window()->display()->isKeyboardAvailable())
            m_ukuiShellSurfaceV1->window()->display()->handleWindowActivated(m_ukuiShellSurfaceV1->window());

        if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)
            && !m_ukuiShellSurfaceV1->window()->display()->isKeyboardAvailable())
            m_ukuiShellSurfaceV1->window()->display()->handleWindowDeactivated(m_ukuiShellSurfaceV1->window());

        m_ukuiShellSurfaceV1->window()->handleWindowStatesChanged(m_pending.states);

        if (m_pending.size.isEmpty()) {
            // An empty size in the configure means it's up to the client to choose the size
            bool normalPending = !(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen));
            if (normalPending && !m_normalSize.isEmpty())
                m_ukuiShellSurfaceV1->window()->resizeFromApplyConfigure(m_normalSize);
        } else {
            m_ukuiShellSurfaceV1->window()->resizeFromApplyConfigure(m_pending.size);
        }
    } else if (m_ukuiShellSurface) {
        if (!(m_applied.states & (Qt::WindowMaximized | Qt::WindowFullScreen)))
            m_normalSize = m_ukuiShellSurface->window()->windowFrameGeometry().size();

        if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)
            && !m_ukuiShellSurface->window()->display()->isKeyboardAvailable())
            m_ukuiShellSurface->window()->display()->handleWindowActivated(m_ukuiShellSurface->window());

        if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)
            && !m_ukuiShellSurface->window()->display()->isKeyboardAvailable())
            m_ukuiShellSurface->window()->display()->handleWindowDeactivated(m_ukuiShellSurface->window());

        m_ukuiShellSurface->window()->handleWindowStatesChanged(m_pending.states);

        if (m_pending.size.isEmpty()) {
            // An empty size in the configure means it's up to the client to choose the size
            bool normalPending = !(m_pending.states & (Qt::WindowMaximized | Qt::WindowFullScreen));
            if (normalPending && !m_normalSize.isEmpty())
                m_ukuiShellSurface->window()->resizeFromApplyConfigure(m_normalSize);
        } else {
            m_ukuiShellSurface->window()->resizeFromApplyConfigure(m_pending.size);
        }
    }

    m_applied = m_pending;
    qCDebug(UKUI_SHELL) << "Applied pending xdg_toplevel configure event:" << m_applied.size << m_applied.states;
}

bool Toplevel::wantsDecorations()
{
    if (m_decoration && (m_decoration->pending() == XdgToplevelDecorationV1::mode_server_side || !m_decoration->isConfigured()))
        return false;

    return !(m_pending.states & Qt::WindowFullScreen);
}

void Toplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states)
{
    m_pending.size = QSize(width, height);

    auto *xdgStates = static_cast<uint32_t *>(states->data);
    size_t numStates = states->size / sizeof(uint32_t);

    m_pending.states = Qt::WindowNoState;

    for (size_t i = 0; i < numStates; i++) {
        switch (xdgStates[i]) {
        case XDG_TOPLEVEL_STATE_ACTIVATED:
            m_pending.states |= Qt::WindowActive;
            break;
        case XDG_TOPLEVEL_STATE_MAXIMIZED:
            m_pending.states |= Qt::WindowMaximized;
            break;
        case XDG_TOPLEVEL_STATE_FULLSCREEN:
            m_pending.states |= Qt::WindowFullScreen;
            break;
        default:
            break;
        }
    }
    qCDebug(UKUI_SHELL) << "Received xdg_toplevel.configure with" << m_pending.size
                        << "and" << m_pending.states;
}

void Toplevel::xdg_toplevel_close()
{
    if (m_ukuiShellSurfaceV1)
        m_ukuiShellSurfaceV1->window()->window()->close();
    else if (m_ukuiShellSurface)
        m_ukuiShellSurface->window()->window()->close();
}

void Toplevel::requestWindowFlags(Qt::WindowFlags flags)
{
    if (m_decoration) {
        if (flags & Qt::FramelessWindowHint) {
            delete m_decoration;
            m_decoration = nullptr;
        } else {
            m_decoration->unsetMode();
        }
    }
}

void Toplevel::requestWindowStates(Qt::WindowStates states)
{
    // Re-send what's different from the applied state
    Qt::WindowStates changedStates = m_applied.states ^ states;

    if (changedStates & Qt::WindowMaximized) {
        if (states & Qt::WindowMaximized)
            set_maximized();
        else
            unset_maximized();
    }

    if (changedStates & Qt::WindowFullScreen) {
        if (states & Qt::WindowFullScreen) {
            QWaylandScreen * screen = nullptr;
            if (m_ukuiShellSurfaceV1)
                screen = m_ukuiShellSurfaceV1->window()->waylandScreen();
            else if (m_ukuiShellSurface)
                screen = m_ukuiShellSurface->window()->waylandScreen();
            if (screen) {
                set_fullscreen(screen->output());
            }
        } else
            unset_fullscreen();
    }

    // Minimized state is not reported by the protocol, so always send it
    if (states & Qt::WindowMinimized) {
        set_minimized();
        if (m_ukuiShellSurfaceV1)
            m_ukuiShellSurfaceV1->window()->handleWindowStatesChanged(states);
        else if (m_ukuiShellSurface)
            m_ukuiShellSurface->window()->handleWindowStatesChanged(states);
    }
}

QtWayland::xdg_toplevel::resize_edge Toplevel::convertToResizeEdges(Qt::Edges edges)
{
    return static_cast<enum resize_edge>(
        ((edges & Qt::TopEdge) ? resize_edge_top : 0)
        | ((edges & Qt::BottomEdge) ? resize_edge_bottom : 0)
        | ((edges & Qt::LeftEdge) ? resize_edge_left : 0)
        | ((edges & Qt::RightEdge) ? resize_edge_right : 0));
}

} // namespace QtWaylandClient
