// Copyright (C) 2024-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 "rootsurfacecontainer.h"

#include "common/treelandlogging.h"
#include "output/output.h"
#include "seat/helper.h"
#include "seat/seatsmanager.h"
#include "surface/surfacewrapper.h"
#include "treelandconfig.hpp"
#include "treelanduserconfig.hpp"
#include "workspace/workspace.h"

#include <wcursor.h>
#include <woutput.h>
#include <wscoplistener.h>
#include <woutputitem.h>
#include <woutputlayout.h>
#include <wxdgpopupsurface.h>
#include <wxdgtoplevelsurface.h>
#include <wseat.h>
#include <winputdevice.h>

#include <QPointer>
#include <QQuickWindow>

WAYLIB_SERVER_USE_NAMESPACE

namespace {

void setPrimaryOutputConfig(Output *output)
{
    auto *helper = Helper::instance();
    if (!helper)
        return;

    auto *config = helper->globalConfig();
    auto setConfig = [config, output = QPointer<Output>(output)] {
        config->setPrimaryOutputId(output->getOutputId());
    };

    if (config->isInitializeSucceeded()) {
        setConfig();
    } else if (!config->isInitializeFailed()) {
        QObject::connect(config,
                         &TreelandConfig::configInitializeSucceed,
                         helper,
                         setConfig,
                         Qt::SingleShotConnection);
    }
}

}

OutputListModel::OutputListModel(QObject *parent)
    : ObjectListModel("output", parent)
{
}

RootSurfaceContainer::RootSurfaceContainer(QQuickItem *parent)
    : SurfaceContainer(parent)
    , m_outputModel(new OutputListModel(this))
    , m_cursor(new WCursor(this))
{
    m_cursor->setEventWindow(window());
}

RootSurfaceContainer::~RootSurfaceContainer()
{
    // Clean up per-seat containers
    qDeleteAll(m_seatContainers);
    m_seatContainers.clear();
}

void RootSurfaceContainer::init(WServer *server)
{
    m_outputLayout = new WOutputLayout(server);
    m_cursor->setLayout(m_outputLayout);
    connect(m_outputLayout, &WOutputLayout::implicitWidthChanged, this, [this] {
        const auto width = m_outputLayout->implicitWidth();
        window()->setWidth(width);
        setWidth(width);
    });

    connect(m_outputLayout, &WOutputLayout::implicitHeightChanged, this, [this] {
        const auto height = m_outputLayout->implicitHeight();
        window()->setHeight(height);
        setHeight(height);
    });

    // Listen on the native wlr_output_layout change signal (fires on add,
    // remove, reposition and commit-triggered reconfiguration), matching the
    // baseline qw_output_layout::notify_change wiring.
    m_outputLayoutListenerOwner = std::make_unique<WListenerOwner>();
    m_outputLayout->listeners(m_outputLayoutListenerOwner.get())->add(
        &m_outputLayout->handle()->events.change, this, [this] {
        for (auto output : std::as_const(outputs())) {
            output->updatePositionFromLayout();
        }
        ensureCursorVisible();

        // for (auto s : m_surfaceContainer->surfaces()) {
        //     ensureSurfaceNormalPositionValid(s);
        //     updateSurfaceOutputs(s);
        // }
    });

    m_dragSurfaceItem = new WSurfaceItem(window()->contentItem());
    m_dragSurfaceItem->setZ(
        static_cast<std::underlying_type_t<WOutputLayout::Layer>>(WOutputLayout::Layer::Cursor)
        - 1);
    m_dragSurfaceItem->setFlags(WSurfaceItem::DontCacheLastBuffer);

    QObject::connect(m_cursor, &WCursor::positionChanged, this, [this] {
        m_dragSurfaceItem->setPosition(m_cursor->position());
    });

    QObject::connect(m_cursor, &WCursor::requestedDragSurfaceChanged, this, [this] {
        m_dragSurfaceItem->setSurface(m_cursor->requestedDragSurface());
    });

    setupSeatManagement();
}

SurfaceWrapper *RootSurfaceContainer::getSurface(WSurface *surface) const
{
    const auto surfaces = this->surfaces();
    for (const auto &wrapper : surfaces) {
        if (wrapper->surface() == surface)
            return wrapper;
    }
    return nullptr;
}

SurfaceWrapper *RootSurfaceContainer::getSurface(WToplevelSurface *surface) const
{
    const auto surfaces = this->surfaces();
    for (const auto &wrapper : surfaces) {
        if (wrapper->shellSurface() == surface)
            return wrapper;
    }
    return nullptr;
}

void RootSurfaceContainer::destroyForSurface(SurfaceWrapper *wrapper)
{
    // Clean up per-seat state for this surface
    for (auto *container : std::as_const(m_seatContainers)) {
        container->surfaceDestroyed(wrapper);
    }

    wrapper->destroy();
}

void RootSurfaceContainer::addOutput(Output *output)
{
    m_outputModel->addObject(output);
    m_outputLayout->autoAdd(output->output());
    if (!m_primaryOutput)
        setPrimaryOutput(output);

    // Register all outputs (including primary and proxy/copy) to ensure
    // LayerSurfaceContainer is initialized for every active display.
    SurfaceContainer::addOutput(output);

    // Layer surfaces are re-entered by LayerSurfaceContainer::addOutput
    // which calls setOutputs. Skip them here to avoid double re-enter.
    for (auto s : std::as_const(surfaces())) {
        if (s->type() == SurfaceWrapper::Type::Layer)
            continue;
        updateSurfaceOutputs(s);
    }
}

void RootSurfaceContainer::removeOutput(Output *output)
{
    m_outputModel->removeObject(output);
    SurfaceContainer::removeOutput(output);

    for (auto *container : std::as_const(m_seatContainers)) {
        if (container->moveResizeSurface() &&
            container->moveResizeSurface()->ownsOutput() == output) {
            container->endMoveResize();
        }
    }

    m_outputLayout->remove(output->output());
    if (m_primaryOutput == output) {
        const auto outputs = m_outputLayout->outputs();
        if (!outputs.isEmpty()) {
            auto newPrimaryOutput = Helper::instance()->getOutput(outputs.first());
            setPrimaryOutput(newPrimaryOutput);
        }
    }

    // ensure cursor within output
    const auto outputPos = output->outputItem()->position();
    if (output->geometry().contains(m_cursor->position()) && m_primaryOutput) {
        const auto posInOutput = m_cursor->position() - outputPos;
        const auto newCursorPos = m_primaryOutput->outputItem()->position() + posInOutput;

        if (m_primaryOutput->geometry().contains(newCursorPos))
            Helper::instance()->setCursorPosition(newCursorPos);
        else
            Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center());
    }
}

void RootSurfaceContainer::beginMoveResize(SurfaceWrapper *surface, Qt::Edges edges)
{
    // Delegate to default seat (seats.first())
    beginMoveResizeForSeat(nullptr, surface, edges);
}

void RootSurfaceContainer::doMoveResize(const QPointF &incrementPos)
{
    // Delegate to default seat (seats.first())
    doMoveResizeForSeat(nullptr, incrementPos);
}

void RootSurfaceContainer::cancelMoveResize(SurfaceWrapper *surface)
{
    // Check all seat containers
    for (auto *container : std::as_const(m_seatContainers)) {
        if (container->moveResizeSurface() == surface) {
            container->endMoveResize();
            return;
        }
    }
}

void RootSurfaceContainer::endMoveResize()
{
    endMoveResizeForSeat(nullptr);
}

SurfaceWrapper *RootSurfaceContainer::moveResizeSurface() const
{
    return getMoveResizeSurfaceForSeat(nullptr);
}

void RootSurfaceContainer::startMove(SurfaceWrapper *surface)
{
    beginMoveResizeForSeat(nullptr, surface, Qt::Edges{});
    Helper::instance()->activateSurface(surface);
}

void RootSurfaceContainer::startResize(SurfaceWrapper *surface, Qt::Edges edges)
{
    Q_ASSERT(edges != 0);
    beginMoveResizeForSeat(nullptr, surface, edges);

    surface->shellSurface()->setResizeing(true);
    Helper::instance()->activateSurface(surface);
}

void RootSurfaceContainer::addSurface(SurfaceWrapper *)
{
    Q_UNREACHABLE_RETURN();
}

void RootSurfaceContainer::removeSurface(SurfaceWrapper *)
{
    Q_UNREACHABLE_RETURN();
}

void RootSurfaceContainer::addBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface)
{
    SurfaceContainer::addBySubContainer(sub, surface);

    if (surface->type() != SurfaceWrapper::Type::Layer) {
        // RootSurfaceContainer does not have control over layer surface's position and ownsOutput
        // All things are done in LayerSurfaceContainer

        setupSurfaceRequestHandlers(surface);

        if (!surface->ownsOutput()) {
            auto parentSurface = surface->parentSurface();
            auto output = parentSurface ? parentSurface->ownsOutput() : primaryOutput();

            if (auto xdgPopupSurface = qobject_cast<WXdgPopupSurface *>(surface->shellSurface())) {
                if (parentSurface->type() != SurfaceWrapper::Type::Layer) {
                    // If parentSurface is Layer surface, follow parentSurface->ownsOutput
                    auto pos = parentSurface->position() + parentSurface->surfaceItem()->position()
                        + xdgPopupSurface->getPopupPosition();
                    if (auto op = wlr_output_layout_output_at(m_outputLayout->handle(), pos.x(), pos.y()))
                        output =
                            Helper::instance()->getOutput(WOutput::fromHandle(op));
                }
            }
            surface->setOwnsOutput(output);
        }

        connect(surface, &SurfaceWrapper::geometryChanged, this, [this, surface] {
            updateSurfaceOutputs(surface);
        });

        updateSurfaceOutputs(surface);
    }
}

void RootSurfaceContainer::removeBySubContainer(SurfaceContainer *sub, SurfaceWrapper *surface)
{
    // End move/resize for this surface in all containers
    for (auto *container : std::as_const(m_seatContainers)) {
        if (container->moveResizeSurface() == surface) {
            container->endMoveResize();
        }
    }

    SurfaceContainer::removeBySubContainer(sub, surface);
}

bool RootSurfaceContainer::filterSurfaceGeometryChanged(SurfaceWrapper *surface,
                                                        QRectF &newGeometry,
                                                        const QRectF &oldGeometry)
{
    // Helper function to adjust anchor-based position during resize
    // When resizing from an edge (e.g., right edge), the opposite position remains fixed
    auto adjustForResize = [](QRectF &geometry,
                              const QRectF &oldGeometry,
                              Qt::Edges edges) {
        if (edges & Qt::RightEdge)
            geometry.moveLeft(oldGeometry.left());
        if (edges & Qt::BottomEdge)
            geometry.moveTop(oldGeometry.top());
        if (edges & Qt::LeftEdge)
            geometry.moveRight(oldGeometry.right());
        if (edges & Qt::TopEdge)
            geometry.moveBottom(oldGeometry.bottom());
    };

    // Check all per-seat containers for move/resize state
    for (auto *container : std::as_const(m_seatContainers)) {
        auto &mrState = container->moveResizeState();
        if (mrState.surface == surface) {
            const auto &edges = mrState.edges;
            if (edges != 0 && mrState.settingPositionFlag) {
                // CRITICAL: Return true ONLY during recursive updates to prevent infinite loops
                Q_ASSERT(newGeometry.size() == oldGeometry.size());
                return true;
            }

            if (edges != 0) {
                // Adjust anchor-based position during resize
                QRectF adjustedGeo = newGeometry;
                adjustForResize(adjustedGeo, oldGeometry, edges);

                if (adjustedGeo.topLeft() != newGeometry.topLeft()) {
                    newGeometry = adjustedGeo;
                    mrState.settingPositionFlag = true;
                    QPointF alignedPos = surface->alignToPixelGrid(newGeometry.topLeft());
                    newGeometry.moveTopLeft(alignedPos);
                    surface->setPosition(alignedPos);
                    mrState.settingPositionFlag = false;
                }
                return false;
            }
        }
    }

    return false;
}

bool RootSurfaceContainer::filterSurfaceStateChange(SurfaceWrapper *surface,
                                                    [[maybe_unused]] SurfaceWrapper::State newState,
                                                    [[maybe_unused]] SurfaceWrapper::State oldState)
{
    // Check all per-seat containers for move/resize state
    for (auto *container : std::as_const(m_seatContainers)) {
        const auto &mrState = container->moveResizeState();
        if (mrState.surface == surface && mrState.edges != 0)
            return true;
    }

    return false;
}

WOutputLayout *RootSurfaceContainer::outputLayout() const
{
    return m_outputLayout;
}

WCursor *RootSurfaceContainer::cursor() const
{
    return m_cursor;
}

Output *RootSurfaceContainer::cursorOutput() const
{
    Q_ASSERT(m_cursor->layout() == m_outputLayout);
    return outputAt(m_cursor->position());
}

Output *RootSurfaceContainer::outputAt(const QPointF &pos) const
{
    auto o = wlr_output_layout_output_at(m_outputLayout->handle(), pos.x(), pos.y());
    if (!o)
        return nullptr;

    return Helper::instance()->getOutput(WOutput::fromHandle(o));
}

Output *RootSurfaceContainer::primaryOutput() const
{
    return m_primaryOutput;
}

void RootSurfaceContainer::setPrimaryOutput(Output *newPrimaryOutput, bool updateDconfig)
{
    if (m_primaryOutput == newPrimaryOutput)
        return;

    m_primaryOutput = newPrimaryOutput;
    Q_EMIT primaryOutputChanged();
    if (updateDconfig)
        setPrimaryOutputConfig(newPrimaryOutput);
}

const QList<Output *> &RootSurfaceContainer::outputs() const
{
    return m_outputModel->objects();
}

void RootSurfaceContainer::ensureCursorVisible()
{
    const auto cursorPos = m_cursor->position();
    if (wlr_output_layout_output_at(m_outputLayout->handle(), cursorPos.x(), cursorPos.y()))
        return;

    if (m_primaryOutput) {
        Helper::instance()->setCursorPosition(m_primaryOutput->geometry().center());
    }
}

void RootSurfaceContainer::updateSurfaceOutputs(SurfaceWrapper *surface)
{
    QList<WOutput *> outputs;

    if (surface->type() != SurfaceWrapper::Type::Layer) {
        for (auto *output : m_outputModel->objects()) {
            if (!output->output()->isEnabled())
                continue;

            outputs.append(output->output());
            if (outputs.size() > 1)
                break;
        }

        if (outputs.size() != 1) {
            const QRectF geometry = surface->geometry();
            outputs = m_outputLayout->getIntersectedOutputs(geometry.toRect());
            outputs.removeIf([](WOutput *output) {
                return !output->isEnabled();
            });
        }
    } else {
        const QRectF geometry = surface->geometry();
        outputs = m_outputLayout->getIntersectedOutputs(geometry.toRect());
    }
    surface->setOutputs(outputs);

    if (auto *ws = Helper::instance()->workspace())
        ws->updateSurfaceOwnsOutput(surface);
}

static qreal pointToRectMinDistance(const QPointF &pos, const QRectF &rect)
{
    if (rect.contains(pos))
        return 0;
    return std::min({ std::abs(rect.x() - pos.x()),
                      std::abs(rect.y() - pos.y()),
                      std::abs(rect.right() - pos.x()),
                      std::abs(rect.bottom() - pos.y()) });
}

static QRectF adjustRectToMakePointVisible(const QRectF &inputRect,
                                           const QPointF &absolutePoint,
                                           const QList<QRectF> &visibleAreas)
{
    Q_ASSERT(inputRect.contains(absolutePoint));
    QRectF adjustedRect = inputRect;

    QRectF targetRect;
    qreal distanceToTargetRect = std::numeric_limits<qreal>::max();
    for (const QRectF &area : visibleAreas) {
        Q_ASSERT(!area.isEmpty());
        if (area.contains(absolutePoint))
            return adjustedRect;
        const auto distance = pointToRectMinDistance(absolutePoint, area);
        if (distance < distanceToTargetRect) {
            distanceToTargetRect = distance;
            targetRect = area;
        }
    }
    Q_ASSERT(!targetRect.isEmpty());

    if (absolutePoint.x() < targetRect.x())
        adjustedRect.moveLeft(adjustedRect.x() + targetRect.x() - absolutePoint.x());
    else if (absolutePoint.x() > targetRect.right())
        adjustedRect.moveRight(adjustedRect.right() + targetRect.right() - absolutePoint.x());

    if (absolutePoint.y() < targetRect.y())
        adjustedRect.moveTop(adjustedRect.y() + targetRect.y() - absolutePoint.y());
    else if (absolutePoint.y() > targetRect.bottom())
        adjustedRect.moveBottom(adjustedRect.bottom() + targetRect.bottom() - absolutePoint.y());

    return adjustedRect;
}

void RootSurfaceContainer::ensureSurfaceNormalPositionValid(SurfaceWrapper *surface)
{
    if (surface->type() == SurfaceWrapper::Type::Layer)
        return;

    auto normalGeo = surface->normalGeometry();
    if (normalGeo.size().isEmpty())
        return;

    auto output = surface->ownsOutput();
    if (!output)
        return;

    QList<QRectF> outputRects;
    qreal minScreenX = std::numeric_limits<qreal>::max();
    outputRects.reserve(outputs().size());
    for (auto o : std::as_const(outputs())) {
        outputRects << o->validGeometry();
        minScreenX = qMin(minScreenX, o->validGeometry().left());
    }

    // Ensure window is not outside the screen
    QPointF mustVisiblePosOfSurface(qMin(normalGeo.right(), normalGeo.x() + 20),
                                    qMin(normalGeo.bottom(), normalGeo.y() + 20));

    if (normalGeo.x() < minScreenX) {
        mustVisiblePosOfSurface.setX(qMax(normalGeo.left(), normalGeo.right() - 20));
    }

    normalGeo = adjustRectToMakePointVisible(normalGeo, mustVisiblePosOfSurface, outputRects);

    // Ensure titlebar is not above the screen
    const auto titlebarGeometry = surface->titlebarGeometry().translated(normalGeo.topLeft());
    if (titlebarGeometry.isValid()) {
        for (auto r : std::as_const(outputRects)) {
            if ((r & titlebarGeometry).isEmpty())
                continue;
            if (titlebarGeometry.top() < r.top()) {
                normalGeo.moveTop(normalGeo.top() + r.top() - titlebarGeometry.top());
                break; // Only need to adjust once based on the intersected output
            }
        }
    }

    surface->moveNormalGeometryInOutput(normalGeo.topLeft());
}

OutputListModel *RootSurfaceContainer::outputModel() const
{
    return m_outputModel;
}

void RootSurfaceContainer::moveSurfacesToOutput(const QList<SurfaceWrapper *> &surfaces,
                                                Output *targetOutput,
                                                Output *sourceOutput)
{
    if (!surfaces.isEmpty() && targetOutput) {
        const QRectF targetGeometry = targetOutput->geometry();

        for (auto *surface : surfaces) {
            if (!surface)
                continue;

            const QSizeF size = surface->size();
            QPointF newPos;

            if (surface->ownsOutput() == targetOutput) {
                newPos = surface->position();
            } else {
                const QRectF sourceGeometry =
                    sourceOutput ? sourceOutput->geometry() : surface->ownsOutput()->geometry();
                const QPointF relativePos = surface->position() - sourceGeometry.center();
                newPos = targetGeometry.center() + relativePos;
                surface->setOwnsOutput(targetOutput);
            }
            if (newPos.x() + size.width() > targetGeometry.right())
                newPos.setX(targetGeometry.right() - size.width());
            if (newPos.x() < targetGeometry.left())
                newPos.setX(targetGeometry.left());

            if (newPos.y() + size.height() > targetGeometry.bottom())
                newPos.setY(targetGeometry.bottom() - size.height());
            if (newPos.y() < targetGeometry.top())
                newPos.setY(targetGeometry.top());

            surface->setPosition(newPos);
        }
    }
}

void RootSurfaceContainer::setupSeatManagement()
{
    auto *helper = Helper::instance();
    if (!helper || !helper->seatManager()) {
        qCWarning(lcTlCore) << "Helper or seatManager not available for seat management setup";
        return;
    }

    auto *seatManager = helper->seatManager();
    const auto &seats = seatManager->seats();

    if (seats.isEmpty()) {
        qCWarning(lcTlCore) << "No seats available for seat management setup";
        return;
    }

    for (auto *seat : std::as_const(seats)) {
        onSeatAdded(seat);
    }

    // Connect to seat lifecycle signals
    connect(seatManager, &SeatsManager::seatAdded, this, &RootSurfaceContainer::onSeatAdded);
    connect(seatManager, &SeatsManager::seatRemoved, this, &RootSurfaceContainer::onSeatRemoved);
}

void RootSurfaceContainer::setupSurfaceRequestHandlers(SurfaceWrapper *surface)
{
    connect(
        surface,
        &SurfaceWrapper::moveRequested,
        this,
        [this, surface]() {
            WSeat *requestingSeat = determineSeatForRequest(surface);
            if (!requestingSeat) {
                qCWarning(lcTlCore) << "No seat available for move request";
                return;
            }

            beginMoveResizeForSeat(requestingSeat, surface, Qt::Edges{});
            auto *helper = Helper::instance();
            if (helper) {
                helper->activateSurface(surface);
            }
        },
        Qt::DirectConnection);

    connect(
        surface,
        &SurfaceWrapper::resizeRequested,
        this,
        [this, surface](Qt::Edges edges) {
            WSeat *requestingSeat = determineSeatForRequest(surface);
            if (!requestingSeat) {
                qCWarning(lcTlCore) << "No seat available for resize request";
                return;
            }

            beginMoveResizeForSeat(requestingSeat, surface, edges);
            if (auto *sh = surface->shellSurface()) {
                sh->setResizeing(true);
            }
            auto *helper = Helper::instance();
            if (helper) {
                helper->activateSurface(surface);
            }
        },
        Qt::DirectConnection);
}

WSeat *RootSurfaceContainer::determineSeatForRequest(SurfaceWrapper *surface)
{
    auto *helper = Helper::instance();
    if (!helper || !helper->seatManager()) {
        return nullptr;
    }

    if (auto *currentEventSeat = helper->currentEventSeat()) {
        return currentEventSeat;
    }

    WSeat *lastInteractingSeat = helper->getLastInteractingSeat(surface);
    if (lastInteractingSeat) {
        return lastInteractingSeat;
    }

    const auto &seats = helper->seatManager()->seats();
    if (!seats.isEmpty()) {
        return seats.first();
    }

    return nullptr;
}

void RootSurfaceContainer::onSeatAdded(WSeat *seat)
{
    if (m_seatContainers.contains(seat))
        return;

    auto *container = new SeatSurfaceManager(seat, this);
    m_seatContainers[seat] = container;

    connect(container, &SeatSurfaceManager::moveResizeChanged, this, &RootSurfaceContainer::moveResizeFinised);
    connect(container, &SeatSurfaceManager::activatedSurfaceChanged, this, [seat]() {
        auto *helper = Helper::instance();
        if (helper && helper->seat() == seat) {
            Q_EMIT helper->activatedSurfaceChanged();
        }
    });
}

void RootSurfaceContainer::onSeatRemoved(WSeat *seat)
{
    auto *container = m_seatContainers.take(seat);
    if (container) {
        qCDebug(lcTlCore) << "Removing SeatContainer for seat:" << seat;
        container->deleteLater();
    }
}

SeatSurfaceManager *RootSurfaceContainer::getSeatContainer(WSeat *seat) const
{
    return m_seatContainers.value(seat, nullptr);
}

WSeat *RootSurfaceContainer::getDefaultSeat() const
{
    auto *helper = Helper::instance();
    if (!helper || !helper->seatManager()) {
        qCWarning(lcTlCore) << "Helper or seatManager not available";
        return nullptr;
    }

    auto *seatManager = helper->seatManager();

    // Prefer the explicitly designated fallback/primary seat
    if (WSeat *fallback = seatManager->fallbackSeat()) {
        return fallback;
    }

    // Fallback: use first from list (QMap order, not predictable — warn)
    const auto &seats = seatManager->seats();
    if (seats.isEmpty()) {
        qCCritical(lcTlCore) << "No seats available - this should not happen in Wayland!";
        return nullptr;
    }
    qCWarning(lcTlCore) << "fallbackSeat() is null, using first seat from seats() as default";
    return seats.first();
}

SeatSurfaceManager *RootSurfaceContainer::getSeatContainerOrDefault(WSeat *seat) const
{
    if (!seat) {
        seat = getDefaultSeat();
        if (!seat) {
            qCCritical(lcTlCore) << "Cannot get default seat";
            return nullptr;
        }
    }

    auto *container = m_seatContainers.value(seat, nullptr);
    if (!container) {
        qCWarning(lcTlCore) << "No container for seat:" << seat;
    }

    return container;
}

void RootSurfaceContainer::dismissAllPopups()
{
    for (auto *container : std::as_const(m_seatContainers)) {
        container->dismissPopups();
    }
}

void RootSurfaceContainer::givePopupFocus(SurfaceWrapper *popupWrapper)
{
    if (!popupWrapper)
        return;

    // Route to the correct seat based on wlr_xdg_popup->seat.
    auto *popupSurface = qobject_cast<WXdgPopupSurface *>(popupWrapper->shellSurface());
    if (!popupSurface)
        return;

    auto *wlrPopup = popupSurface->handle();
    if (!wlrPopup || !wlrPopup->seat)
        return;

    for (auto it = m_seatContainers.constBegin(); it != m_seatContainers.constEnd(); ++it) {
        if (it.key()->handle() == wlrPopup->seat) {
            it.value()->givePopupFocus(popupWrapper);
            return;
        }
    }
}

void RootSurfaceContainer::beginMoveResizeForSeat(WSeat *seat, SurfaceWrapper *surface, Qt::Edges edges)
{
    if (!surface)
        return;

    auto *container = getSeatContainerOrDefault(seat);
    if (!container)
        return;

    if (container->moveResizeState().surface &&
        container->moveResizeState().surface != surface) {
        container->endMoveResize();
    }

    const auto state = surface->surfaceState();
    const bool wasTiled = edges == Qt::Edges()
        && (state == SurfaceWrapper::State::Tiling || state == SurfaceWrapper::State::Maximized);
    const QRectF tiledGeo = wasTiled ? surface->geometry() : QRectF();

    container->beginMoveResize(surface, edges);

    WSeat *actualSeat = seat ? seat : getDefaultSeat();
    const bool hasCursor = actualSeat && actualSeat->cursor();
    const QPointF cursor = hasCursor ? actualSeat->cursor()->position() : QPointF();

    // De-tile: restore saved size, reposition so the cursor stays at the same
    // relative grab ratio within the window
    if (wasTiled && !tiledGeo.isEmpty() && hasCursor
        && container->moveResizeState().surface == surface) {
        const QSizeF restoredSize = surface->normalGeometry().isValid()
            ? surface->normalGeometry().size()
            : tiledGeo.size();
        const qreal fx =
            tiledGeo.width() > 0 ? (cursor.x() - tiledGeo.left()) / tiledGeo.width() : 0;
        const qreal fy =
            tiledGeo.height() > 0 ? (cursor.y() - tiledGeo.top()) / tiledGeo.height() : 0;
        QPointF newPos(cursor.x() - fx * restoredSize.width(),
                       cursor.y() - fy * restoredSize.height());
        newPos = surface->alignToPixelGrid(newPos);
        surface->setPosition(newPos);
        container->moveResizeState().startGeometry = QRectF(newPos, restoredSize);
    }

    if (actualSeat && actualSeat->cursor())
        container->moveResizeState().initialPosition = cursor;
}

void RootSurfaceContainer::doMoveResizeForSeat(WSeat *seat, const QPointF &delta)
{
    auto *container = getSeatContainerOrDefault(seat);
    if (container) {
        container->doMoveResize(delta);
    }
}

QQuickItem *RootSurfaceContainer::ensureEdgeTilePreview()
{
    if (m_edgeTilePreview)
        return m_edgeTilePreview;

    auto *engine = Helper::instance()->qmlEngine();
    if (!engine)
        return nullptr;

    m_edgeTilePreview = engine->createEdgeTilePreview(this);
    if (m_edgeTilePreview)
        m_edgeTilePreview->setZ(OverlayZOrder);
    return m_edgeTilePreview;
}

void RootSurfaceContainer::updateEdgeTilePreview(QuickTile::Mode mode, Output *out)
{
    auto *preview = ensureEdgeTilePreview();
    if (!preview)
        return;

    if (mode == QuickTile::Mode::None || !out) {
        preview->setVisible(false);
        return;
    }

    const QRectF geo = QuickTile::geometry(mode, out);
    if (!geo.isValid()) {
        preview->setVisible(false);
        return;
    }

    preview->setX(geo.x());
    preview->setY(geo.y());
    preview->setWidth(geo.width());
    preview->setHeight(geo.height());
    preview->setVisible(true);
}

void RootSurfaceContainer::detectEdgeTilingForSeat(WSeat *seat)
{
    auto *container = getSeatContainerOrDefault(seat);
    if (!container)
        return;

    auto *cfg = Helper::instance()->config();
    const qreal sideTrigger = cfg ? qreal(cfg->edgeSideTriggerDistance()) : 20.0;
    const qreal topTrigger = cfg ? qreal(cfg->edgeTopTriggerDistance()) : 5.0;
    auto &mrState = container->moveResizeState();
    QuickTile::Mode mode = QuickTile::Mode::None;
    Output *out = nullptr;
    bool innerBorder = false;

    if (mrState.surface && mrState.edges == Qt::Edges()) {
        WSeat *actualSeat = seat ? seat : getDefaultSeat();
        WCursor *cursor = actualSeat ? actualSeat->cursor() : nullptr;
        const QPointF pos = cursor ? cursor->position() : QPointF();
        out = outputAt(pos);
        if (out) {
            const QRectF area = out->validGeometry();
            if (pos.x() <= area.left() + sideTrigger) {
                mode = QuickTile::Mode::Left;
            } else if (pos.x() >= area.right() - sideTrigger) {
                mode = QuickTile::Mode::Right;
            } else if (pos.y() <= area.top() + topTrigger) {
                mode = QuickTile::Mode::Maximize;
            }

            // Multi-screen inner-edge detection:
            // Sample 1px outside the edge; if another output covers that
            // point, this is an inner edge
            if (mode != QuickTile::Mode::None) {
                QPointF samplePt;
                switch (mode) {
                case QuickTile::Mode::Maximize:
                    samplePt = QPointF(pos.x(), area.top() - 1.0);
                    break;
                case QuickTile::Mode::Left:
                    samplePt = QPointF(area.left() - 1.0, pos.y());
                    break;
                case QuickTile::Mode::Right:
                    samplePt = QPointF(area.right(), pos.y());
                    break;
                case QuickTile::Mode::None:
                    break;
                }
                for (Output *o : outputs()) {
                    if (o == out)
                        continue;
                    if (o->geometry().contains(samplePt)) {
                        innerBorder = true;
                        break;
                    }
                }
            }
        }
    }

    // Re-evaluate when the edge state changes, or when the cursor crosses to
    // a different output while the preview is active
    if (mrState.detectedTileMode != mode || mrState.edgeTileInnerBorder != innerBorder
        || out != mrState.detectedTileOutput) {
        mrState.detectedTileMode = mode;
        mrState.edgeTileInnerBorder = innerBorder;
        mrState.detectedTileOutput = out;

        if (mode == QuickTile::Mode::None) {
            container->stopEdgeTileDelay();
            mrState.edgeTilePreviewActive = false;
            updateEdgeTilePreview(QuickTile::Mode::None, nullptr);
        } else if (innerBorder) {
            container->stopEdgeTileDelay();
            mrState.edgeTilePreviewActive = false;
            updateEdgeTilePreview(QuickTile::Mode::None, nullptr);
            container->startEdgeTileDelay();
        } else {
            container->stopEdgeTileDelay();
            mrState.edgeTilePreviewActive = true;
            updateEdgeTilePreview(mode, out);
        }
    }
}

void RootSurfaceContainer::endMoveResizeForSeat(WSeat *seat)
{
    auto *container = getSeatContainerOrDefault(seat);
    if (container) {
        container->endMoveResize();
    }
    updateEdgeTilePreview(QuickTile::Mode::None, nullptr);
}

void RootSurfaceContainer::cancelMoveResizeForSeat(WSeat *seat)
{
    auto *container = getSeatContainerOrDefault(seat);
    if (container) {
        container->cancelMoveResize();
    }
    updateEdgeTilePreview(QuickTile::Mode::None, nullptr);
}

SurfaceWrapper *RootSurfaceContainer::getMoveResizeSurfaceForSeat(WSeat *seat) const
{
    auto *container = getSeatContainerOrDefault(seat);
    return container ? container->moveResizeSurface() : nullptr;
}

void RootSurfaceContainer::setActivatedSurfaceForSeat(WSeat *seat, SurfaceWrapper *surface,
                                                      Qt::FocusReason reason)
{
    auto *container = getSeatContainer(seat);
    if (container) {
        container->setActivatedSurface(surface, reason);
    }
}

SurfaceWrapper *RootSurfaceContainer::getActivatedSurfaceForSeat(WSeat *seat) const
{
    auto *container = getSeatContainer(seat);
    if (container) {
        return container->activatedSurface();
    }
    return nullptr;
}
