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

#include "wcursor.h"
#include "woutput.h"
#include "woutputrenderwindow.h"
#include "woutputviewport.h"
#include "wseat.h"
#include "wsgtextureprovider.h"
#include "wsurface.h"
#include "wscoplistener.h"
#include "wsurfaceitem_p.h"
#include "wayliblogging.h"

#include <private/qquickitem_p.h>

#include <wlr_all.h>

#include <memory>

#include <QPointer>
#include <QQueue>
#include <QQuickWindow>
#include <QSGImageNode>
#include <QSGRenderNode>

WAYLIB_SERVER_BEGIN_NAMESPACE

class Q_DECL_HIDDEN SubsurfaceContainer : public QQuickItem
{
    Q_OBJECT
public:
    explicit SubsurfaceContainer(WSurfaceItem *mainSurface)
        : QQuickItem(mainSurface)
    {
    }

    bool isEmpty() const {
        return m_subsurfaces.empty();
    }

    void deleteAfterEmpty() {
        m_deleteAfterEmpty = true;
    }
Q_SIGNALS:
    void subsurfaceAdded(WAYLIB_SERVER_NAMESPACE::WSurfaceItem *item);
    void subsurfaceRemoved(WAYLIB_SERVER_NAMESPACE::WSurfaceItem *item);

protected:
    void itemChange(ItemChange change, const ItemChangeData &data) override
    {
        switch (change) {
        case QQuickItem::ItemChildAddedChange: {
            auto surfaceItem = static_cast<WSurfaceItem *>(data.item);
            Q_ASSERT(surfaceItem); // TODO: check isSubsurface, may segmentation fault now.
            m_subsurfaces.append(surfaceItem);
            m_deleteAfterEmpty = false;
            Q_EMIT subsurfaceAdded(surfaceItem);
            break;
        }
        case QQuickItem::ItemChildRemovedChange: {
            auto surfaceItem = static_cast<WSurfaceItem *>(data.item);
            Q_ASSERT(surfaceItem && m_subsurfaces.contains(surfaceItem));
            m_subsurfaces.removeOne(surfaceItem);
            Q_EMIT subsurfaceRemoved(surfaceItem);
            if (m_deleteAfterEmpty)
                deleteLater();
            break;
        }
        default:
            break;
        }
    }

private:
    QList<WSurfaceItem *> m_subsurfaces;
    bool m_deleteAfterEmpty {false};
};

class Q_DECL_HIDDEN EventItem : public QQuickItem
{
    Q_OBJECT
public:
    explicit EventItem(WSurfaceItem *parent)
        : QQuickItem(parent) {
        setAcceptHoverEvents(true);
        setAcceptTouchEvents(true);
        setAcceptedMouseButtons(Qt::AllButtons);
        setFlag(QQuickItem::ItemClipsChildrenToShape, true);
        setCursor(WCursor::toQCursor(WGlobal::CursorShape::ClientResource));
    }

    inline bool isValid() const {
        if (!parent())
            return false;

        auto surface = static_cast<WSurfaceItem*>(parent())->surface();
        return surface && surface->handle();
    }

    inline WSurfaceItemPrivate *d() const {
        return WSurfaceItemPrivate::get(static_cast<WSurfaceItem*>(parent()));
    }

    bool contains(const QPointF &point) const override {
        if (Q_UNLIKELY(!isValid()))
            return false;

        return d()->surface->inputRegionContains(point);
    }

private:
    bool event(QEvent *event) override {
        switch(event->type()) {
        using enum QEvent::Type;
        // Don't insert events before MouseButtonPress
        case MouseButtonPress: Q_FALLTHROUGH();
        case MouseButtonRelease: Q_FALLTHROUGH();
        case MouseMove:
            if (static_cast<QMouseEvent*>(event)->source() != Qt::MouseEventNotSynthesized)
                return true; // The non-native events don't send to WSeat
            Q_FALLTHROUGH();
        case HoverMove: Q_FALLTHROUGH();
        case HoverEnter: Q_FALLTHROUGH();
        case HoverLeave: Q_FALLTHROUGH();
        case KeyPress: Q_FALLTHROUGH();
        case KeyRelease: Q_FALLTHROUGH();
        case Wheel: Q_FALLTHROUGH();
        case TouchBegin: Q_FALLTHROUGH();
        case TouchUpdate: Q_FALLTHROUGH();
        case TouchEnd: Q_FALLTHROUGH();
        case TouchCancel: {
            auto e = static_cast<QInputEvent*>(event);
            Q_ASSERT(e);
            // We may receive HoverLeave when WSurfaceItem was destroying
            if (parent() && static_cast<WSurfaceItem*>(parent())->sendEvent(e))
                return true;
            break;
        }
        case NativeGesture: {
            auto e = static_cast<QNativeGestureEvent*>(event);
            Q_ASSERT(e);
            if (parent() && static_cast<WSurfaceItem*>(parent())->sendEvent(e))
                return true;
            break;
        }
        // No need to process focusOut, see [PR 282](https://github.com/vioken/waylib/pull/282)
        default:
            break;
        }

        return QQuickItem::event(event);
    }
};

// Clean RAII wrapper for wlr_buffer that automatically manages lock and n_ignore_locks.
struct Q_DECL_HIDDEN BufferRef
{
    BufferRef() = default;
    ~BufferRef() { release(); }

    BufferRef(const BufferRef &) = delete;
    BufferRef &operator=(const BufferRef &) = delete;

    BufferRef(BufferRef &&other) noexcept {
        std::swap(m_buffer, other.m_buffer);
    }

    BufferRef &operator=(BufferRef &&other) noexcept {
        std::swap(m_buffer, other.m_buffer);
        return *this;
    }

    // Reset to a new buffer (AddRef-before-Release to avoid transient 0 locks)
    void reset(wlr_buffer *newBuf = nullptr) {
        if (m_buffer == newBuf)
            return;
        if (newBuf) {
            wlr_buffer_lock(newBuf);
            if (auto cb = wlr_client_buffer_get(newBuf))
                cb->n_ignore_locks++;
        }
        release();
        m_buffer = newBuf;
    }

    wlr_buffer *get() const { return m_buffer; }
    explicit operator bool() const { return m_buffer != nullptr; }

private:
    void release() {
        if (m_buffer) {
            if (auto cb = wlr_client_buffer_get(m_buffer))
                cb->n_ignore_locks--;
            wlr_buffer_unlock(m_buffer);
            m_buffer = nullptr;
        }
    }

    wlr_buffer *m_buffer = nullptr;
};

class Q_DECL_HIDDEN WSurfaceItemContentPrivate: public QQuickItemPrivate
{
public:
    WSurfaceItemContentPrivate([[maybe_unused]] WSurfaceItemContent *qq){}

    std::unique_ptr<WListenerOwner> surfaceListenerOwner;

    void cleanTextureProvider();

    void invalidate() {
        W_Q(WSurfaceItemContent);
        // Detach from the wlr_surface signals before the wrapper is gone
        // (wlroots asserts empty listener lists on surface destroy).
        // Disconnect unconditionally: setSurface() clears d->surface before
        // calling invalidate(), so the guard below would skip this and leave
        // the old wlr_surface's commit listener attached.
        if (surface && surfaceListenerOwner)
            surface->removeListeners(surfaceListenerOwner.get());
        if (surface) {
            surface->disconnect(q);
            if (textureProvider) {
                surface->disconnect(textureProvider);
            }
            surface = nullptr;
        }

        if (frameDoneConnection)
            QObject::disconnect(frameDoneConnection);

        Q_ASSERT(!updateTextureConnection);

        if (dontCacheLastBuffer) {
            buffer.reset();
            cleanTextureProvider();
            q->update();
        }
    }

    void init() {
        W_Q(WSurfaceItemContent);

        QObject::connect(surface, &WSurface::beforeDestroy, q, [this] {
            invalidate();
        });
        if (!surfaceListenerOwner)
            surfaceListenerOwner = std::make_unique<WListenerOwner>();
        surface->listeners(surfaceListenerOwner.get())->add(&surface->handle()->events.commit, this, &WSurfaceItemContentPrivate::updateSurfaceState);

        Q_ASSERT(!updateTextureConnection);
        updateTextureConnection = QObject::connect(surface, &WSurface::commit,
                                                       q, [q, this] (quint32 committedState) {
            const bool bufferChanged = committedState & WLR_SURFACE_STATE_BUFFER;

            if (bufferChanged) {
                // Get the new buffer pointer from surface
                auto newBuffer = surface->buffer();

                if (!live) {
                    // Non-live mode: defer to pendingBuffer
                    pendingBuffer.reset(newBuffer);
                } else {
                    // Live mode: update buffer immediately
                    buffer.reset(newBuffer);
                    q->update();
                }
            }

            if (Q_LIKELY((q->isVisible() || lastRendered) && live))
                surface->scheduleFrameIfNeeded();
        });

        updateFrameDoneConnection();
        updateSurfaceState();
        rendered = true;
        lastRendered = true;
    }

    void updateFrameDoneConnection() {
        W_Q(WSurfaceItemContent);

        if (frameDoneConnection)
            QObject::disconnect(frameDoneConnection);
        if (!q->window()) // maybe null due to item not fully initialized
            return;

        auto rw = q->outputRenderWindow();
        if (Q_LIKELY(rw)) {
            // wayland protocol job should not run in rendering thread, so set context qobject to contentItem
            frameDoneConnection = QObject::connect(rw, &WOutputRenderWindow::renderEnd,
                                                   q, [this, q] (const QList<QPointer<WOutput>> committedOutputs) {
                                                       lastRendered = rendered;
                                                       if (Q_LIKELY((rendered || q->isVisible()) && live)
                                                           && surface &&
                                                           committedOutputs.contains(surface->framePacingOutput())) {
                                                           surface->notifyFrameDone();
                                                           rendered = false;
                                                       }
                                                   }); // if signal is emitted from seperated rendering thread, default QueuedConnection is used
        } else {
            qCFatal(lcWlSurface) << "Needs a WOutputRenderWindow to render the WSurfaceItemContent, "
                                      "but the current window is:" << q->window();
        }
    }

    void updateSurfaceState() {
        if (!surface)
            return;

        wlr_fbox tmp;
        wlr_surface_get_buffer_source_box(surface->handle(), &tmp);
        auto newBufferSourceBox = QRectF(tmp.x, tmp.y, tmp.width, tmp.height);
        std::swap(newBufferSourceBox, bufferSourceBox);

        W_Q(WSurfaceItemContent);

        const wlr_alpha_modifier_surface_v1_state *alphaModifierState =
            wlr_alpha_modifier_v1_get_surface_state(surface->handle());
        if (alphaModifierState)
            setAlphaModifier(alphaModifierState->multiplier);

        const auto bOffset = surface->bufferOffset();
        if (bOffset != bufferOffset) {
            bufferOffset = surface->bufferOffset();
            Q_EMIT q->bufferOffsetChanged();
        }

        if (bufferSourceBox != newBufferSourceBox) {
            Q_EMIT q->bufferSourceRectChanged();
        }

        const auto s = surface->size();
        q->setImplicitSize(s.width(), s.height());
    }

    inline void swapBufferIfNeeded() {
        if (pendingBuffer)
            buffer = std::move(pendingBuffer);
    }

    inline void setDevicePixelRatio(qreal dpr) {
        if (dpr == devicePixelRatio)
            return;
        devicePixelRatio = dpr;
        Q_EMIT q_func()->devicePixelRatioChanged();
        Q_EMIT q_func()->bufferSourceRectChanged();
    }

    inline void setAlphaModifier(qreal alpha) {
        if (qFuzzyCompare(alphaModifier, alpha))
            return;

        alphaModifier = alpha;

        W_Q(WSurfaceItemContent);

        Q_EMIT q->alphaModifierChanged();
    }

    W_DECLARE_PUBLIC(WSurfaceItemContent)
    QPointer<WSurface> surface;
    QRectF bufferSourceBox;
    QPoint bufferOffset;
    qreal devicePixelRatio = 1.0;
    qreal alphaModifier = 1.0;

    QMetaObject::Connection frameDoneConnection;
    mutable WSGTextureProvider *textureProvider = nullptr;
    BufferRef buffer;
    BufferRef pendingBuffer;
    mutable QMetaObject::Connection updateTextureConnection;
    bool dontCacheLastBuffer = false;
    bool live = true;
    bool ignoreBufferOffset = false;
    bool lastRendered = false;
    QAtomicInteger<bool> rendered = false;
};

WSurfaceItemContent::WSurfaceItemContent(QQuickItem *parent)
    : QQuickItem(*new WSurfaceItemContentPrivate(this), parent)
{
    setFlag(QQuickItem::ItemHasContents, true);
}

WSurfaceItemContent::~WSurfaceItemContent()
{
    W_D(WSurfaceItemContent);
    if (d->updateTextureConnection) {
        Q_ASSERT(d->surface);
        d->surface->disconnect(d->updateTextureConnection);
    }

    if (d->frameDoneConnection)
        QObject::disconnect(d->frameDoneConnection);

    //`d->window` will become nullptr in ~QQuickItem
    // Don't move this to private class
    d->cleanTextureProvider();
}

WSurface *WSurfaceItemContent::surface() const
{
    W_DC(WSurfaceItemContent);
    return d->surface;
}

void WSurfaceItemContent::setSurface(WSurface *surface)
{
    W_D(WSurfaceItemContent);

    // when surface is null and original surface has destroyed also need reset
    if (surface && d->surface == surface)
        return;

    auto oldSurface = d->surface;
    d->surface = surface;
    if (isComponentComplete()) {
        if (oldSurface) {
            oldSurface->disconnect(this);
            if (d->textureProvider)
                oldSurface->disconnect(d->textureProvider);
        }

        if (d->surface)
            d->init();
    }

    if (!d->surface) {
        d->invalidate();
    } else {
        const auto s = surface->size();
        setImplicitSize(s.width(), s.height());
    }

    Q_EMIT surfaceChanged();
}

bool WSurfaceItemContent::isTextureProvider() const
{
    return true;
}

QSGTextureProvider *WSurfaceItemContent::textureProvider() const
{
    if (QQuickItem::isTextureProvider())
        return QQuickItem::textureProvider();

    return wTextureProvider();
}

WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const
{
    W_DC(WSurfaceItemContent);

    auto w = qobject_cast<WOutputRenderWindow*>(d->window);
    if (!w || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) {
        qCWarning(lcWlSurface, "WQuickCursor::textureProvider: can only be queried on the rendering thread of an WOutputRenderWindow");
        return nullptr;
    }

    if (!d->textureProvider) {
        d->textureProvider = new WSGTextureProvider(w);
        d->textureProvider->setSmooth(smooth());
        connect(this, &WSurfaceItemContent::smoothChanged,
                d->textureProvider, &WSGTextureProvider::setSmooth);

        if (d->surface) {
            if (auto texture = wlr_surface_get_texture(d->surface->handle())) {
                d->textureProvider->setTexture(texture, d->buffer.get());
            } else {
                d->textureProvider->setBuffer(d->buffer.get());
            }
        }
    }
    return d->textureProvider;
}

WOutputRenderWindow *WSurfaceItemContent::outputRenderWindow() const
{
    return qobject_cast<WOutputRenderWindow*>(window());
}

bool WSurfaceItemContent::cacheLastBuffer() const
{
    W_DC(WSurfaceItemContent);
    return !d->dontCacheLastBuffer;
}

void WSurfaceItemContent::setCacheLastBuffer(bool newCacheLastBuffer)
{
    W_D(WSurfaceItemContent);
    if (d->dontCacheLastBuffer == !newCacheLastBuffer)
        return;
    d->dontCacheLastBuffer = !newCacheLastBuffer;
    Q_EMIT cacheLastBufferChanged();
}

bool WSurfaceItemContent::live() const
{
    W_DC(WSurfaceItemContent);
    return d->live;
}

void WSurfaceItemContent::setLive(bool live)
{
    W_D(WSurfaceItemContent);
    if (d->live == live)
        return;
    d->live = live;
    if (live) {
        d->swapBufferIfNeeded();
        update();
    }
    Q_EMIT liveChanged();
}

QPoint WSurfaceItemContent::bufferOffset() const
{
    W_DC(WSurfaceItemContent);
    return d->bufferOffset;
}

bool WSurfaceItemContent::ignoreBufferOffset() const
{
    W_DC(WSurfaceItemContent);
    return d->ignoreBufferOffset;
}

void WSurfaceItemContent::setIgnoreBufferOffset(bool newIgnoreBufferOffset)
{
    W_D(WSurfaceItemContent);
    if (d->ignoreBufferOffset == newIgnoreBufferOffset)
        return;
    d->ignoreBufferOffset = newIgnoreBufferOffset;
    Q_EMIT ignoreBufferOffsetChanged();
}

QRectF WSurfaceItemContent::bufferSourceRect() const
{
    W_DC(WSurfaceItemContent);
    return QRectF(d->bufferSourceBox.topLeft() / d->devicePixelRatio,
                  d->bufferSourceBox.size() / d->devicePixelRatio);
}

qreal WSurfaceItemContent::devicePixelRatio() const
{
    W_DC(WSurfaceItemContent);
    return d->devicePixelRatio;
}

void WSurfaceItemContent::componentComplete()
{
    QQuickItem::componentComplete();

    W_D(WSurfaceItemContent);
    if (d->surface)
        d->init();
}

qreal WSurfaceItemContent::alphaModifier() const
{
    W_DC(WSurfaceItemContent);
    return d->alphaModifier;
}

class Q_DECL_HIDDEN WSGRenderFootprintNode: public QSGRenderNode
{
public:
    WSGRenderFootprintNode(WSurfaceItemContent *owner)
        : QSGRenderNode()
        , m_owner(owner)
    {
        setFlag(QSGNode::OwnedByParent); // parent is fixed, auto release
    }

    ~WSGRenderFootprintNode() {}

    void render(const RenderState*) override
    {
        if (Q_LIKELY(m_owner))
            m_owner->d_func()->rendered = true;
    }

    RenderingFlags flags() const override
    {
        return NoExternalRendering | BoundedRectRendering | DepthAwareRendering | OpaqueRendering;
    }

    QPointer<WSurfaceItemContent> m_owner;
};

QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    W_D(WSurfaceItemContent);

    auto tp = wTextureProvider();
    if (d->live || !tp->texture()) {
        auto texture = d->surface ? wlr_surface_get_texture(d->surface->handle()) : nullptr;
        if (texture) {
            tp->setTexture(texture, d->buffer.get());
        } else {
            tp->setBuffer(d->buffer.get());
        }
    }

    if (!tp->texture() || width() <= 0 || height() <= 0) {
        delete oldNode;
        return nullptr;
    }

    auto node = static_cast<QSGImageNode*>(oldNode);
    if (Q_UNLIKELY(!node)) {
        node = window()->createImageNode();
        node->setOwnsTexture(false);
        QSGNode *fpnode = new WSGRenderFootprintNode(this);
        node->appendChildNode(fpnode);
    }

    auto texture = tp->texture();
    node->setTexture(texture);
    const QRectF textureGeometry = d->bufferSourceBox;
    node->setSourceRect(textureGeometry);
    const QRectF targetGeometry(d->ignoreBufferOffset ? QPointF() : d->bufferOffset, size());
    node->setRect(targetGeometry);
    node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);

    return node;
}

void WSurfaceItemContent::releaseResources()
{
    W_D(WSurfaceItemContent);

    d->cleanTextureProvider();
    d->invalidate();

    // Force to update the contents, avoid to render the invalid textures
    // Only mark dirty if we have a valid window to avoid crashes during window destruction
    if (window()) {
        QQuickItemPrivate::get(this)->dirty(QQuickItemPrivate::Content);
    }
}

void WSurfaceItemContent::itemChange(ItemChange change, const ItemChangeData &data)
{
    QQuickItem::itemChange(change, data);
    W_D(WSurfaceItemContent);
    if (change == QQuickItem::ItemSceneChange) {
        d->updateFrameDoneConnection();
        d->setDevicePixelRatio(data.window ? data.window->effectiveDevicePixelRatio() : 1.0);
    } else if (change == QQuickItem::ItemDevicePixelRatioHasChanged) {
        d->setDevicePixelRatio(data.realValue);
    }
}

void WSurfaceItemContent::invalidateSceneGraph()
{
    W_D(WSurfaceItemContent);
    if (d->textureProvider)
        delete d->textureProvider;
    d->textureProvider = nullptr;
}

WSurfaceItem::WSurfaceItem(QQuickItem *parent)
    : WSurfaceItem(*new WSurfaceItemPrivate(), parent)
{
}

WSurfaceItem::WSurfaceItem(WSurfaceItemPrivate &dd, QQuickItem *parent)
    : QQuickItem(dd, parent)
{
    setFlag(ItemIsFocusScope);
}

WSurfaceItem::~WSurfaceItem()
{

}

QRectF WSurfaceItem::boundingRect() const
{
    W_DC(WSurfaceItem);
    return d->boundingRect;
}

WSurfaceItem *WSurfaceItem::fromFocusObject(QObject *focusObject)
{
    if (auto item = qobject_cast<EventItem*>(focusObject))
        return item->d()->q_func();
    return nullptr;
}

WSurface *WSurfaceItem::surface() const
{
    Q_D(const WSurfaceItem);
    return d->surface.get();
}

void WSurfaceItem::setSurface(WSurface *surface)
{
    Q_D(WSurfaceItem);

    // when surface is null and original surface has destroyed also need reset
    if (surface && d->surface == surface)
        return;

    auto oldSurface = d->surface;
    d->beforeRequestResizeSurfaceStateSeq = 0;
    d->surface = surface;
    if (d->ready) {
        d->ready = false;
        Q_EMIT readyChanged();
    }
    if (d->componentComplete) {
        if (oldSurface) {
            oldSurface->disconnect(this);
        }

        if (d->surface)
            initSurface();
    }

    // TODO: reset subsurfaces?

    if (!d->surface)
        releaseResources();

    if (auto content = d->getItemContent())
        content->setSurface(surface);

    Q_EMIT surfaceChanged();
}

QQuickItem *WSurfaceItem::contentItem() const
{
    Q_D(const WSurfaceItem);
    return d->contentContainer;
}

QQuickItem *WSurfaceItem::eventItem() const
{
    Q_D(const WSurfaceItem);
    return d->eventItem;
}

WSurfaceItem::ResizeMode WSurfaceItem::resizeMode() const
{
    Q_D(const WSurfaceItem);

    return d->resizeMode;
}

void WSurfaceItem::setResizeMode(ResizeMode newResizeMode)
{
    Q_D(WSurfaceItem);

    if (d->resizeMode == newResizeMode)
        return;
    d->resizeMode = newResizeMode;
    Q_EMIT resizeModeChanged();
}

void WSurfaceItem::resize(ResizeMode mode)
{
    Q_D(WSurfaceItem);

    if (mode == ManualResize) {
        qmlWarning(this) << "Can't resize WSurfaceItem for ManualResize mode.";
        return;
    }

    if (!d->surfaceState)
        return;

    if (!d->effectiveVisible)
        return;

    d->doResize(mode);
}

bool WSurfaceItem::effectiveVisible() const
{
    Q_D(const WSurfaceItem);
    return d->effectiveVisible;
}

WSurfaceItem::Flags WSurfaceItem::flags() const
{
    Q_D(const WSurfaceItem);
    return d->surfaceFlags;
}

void WSurfaceItem::setFlags(const Flags &newFlags)
{
    Q_D(WSurfaceItem);

    if (d->surfaceFlags == newFlags)
        return;
    d->surfaceFlags = newFlags;
    d->updateEventItem(false);

    if (auto content = d->getItemContent()) {
        content->setCacheLastBuffer(!newFlags.testFlag(DontCacheLastBuffer));
        content->setLive(!newFlags.testFlag(NonLive));
    }

    for (auto sub : std::as_const(d->subsurfaces))
        sub->setFlags(newFlags);

    Q_EMIT flagsChanged();
}

qreal WSurfaceItem::rightPadding() const
{
    Q_D(const WSurfaceItem);
    return d->paddings.right();
}

void WSurfaceItem::setRightPadding(qreal newRightPadding)
{
    Q_D(WSurfaceItem);
    if (qFuzzyCompare(d->paddings.right(), newRightPadding))
        return;
    d->paddings.setRight(newRightPadding);
    d->onPaddingsChanged();
    setImplicitWidth(d->calculateImplicitWidth());
    Q_EMIT rightPaddingChanged();
}

qreal WSurfaceItem::surfaceSizeRatio() const
{
    Q_D(const WSurfaceItem);
    return d->surfaceSizeRatio;
}

void WSurfaceItem::setSurfaceSizeRatio(qreal ssr)
{
    Q_D(WSurfaceItem);

    if (qFuzzyCompare(ssr, d->surfaceSizeRatio))
        return;
    d->surfaceSizeRatio = ssr;
    Q_EMIT surfaceSizeRatioChanged();

    surfaceSizeRatioChange();
}

qreal WSurfaceItem::bufferScale() const
{
    Q_D(const WSurfaceItem);

    return d->surfaceState ? d->surfaceState->bufferScale : 1.0;
}

QQmlComponent *WSurfaceItem::delegate() const
{
    Q_D(const WSurfaceItem);

    return d->delegate;
}

void WSurfaceItem::setDelegate(QQmlComponent *newDelegate)
{
    Q_D(WSurfaceItem);

    if (d->delegate == newDelegate)
        return;
    d->delegate = newDelegate;
    d->delegateIsDirty = true;
    if (d->componentComplete)
        d->initForDelegate();

    if (flags() & DelegateForSubsurface) {
        for (auto sub : std::as_const(d->subsurfaces))
            sub->setDelegate(newDelegate);
    }

    Q_EMIT delegateChanged();
}

qreal WSurfaceItem::leftPadding() const
{
    Q_D(const WSurfaceItem);
    return d->paddings.left();
}

void WSurfaceItem::setLeftPadding(qreal newLeftPadding)
{
    Q_D(WSurfaceItem);
    if (qFuzzyCompare(d->paddings.left(), newLeftPadding))
        return;
    d->paddings.setLeft(newLeftPadding);
    d->onPaddingsChanged();
    setImplicitWidth(d->calculateImplicitWidth());
    Q_EMIT leftPaddingChanged();
}

qreal WSurfaceItem::bottomPadding() const
{
    Q_D(const WSurfaceItem);
    return d->paddings.bottom();
}

void WSurfaceItem::setBottomPadding(qreal newBottomPadding)
{
    Q_D(WSurfaceItem);
    if (qFuzzyCompare(d->paddings.bottom(), newBottomPadding))
        return;
    d->paddings.setBottom(newBottomPadding);
    d->onPaddingsChanged();
    setImplicitHeight(d->calculateImplicitHeight());
    Q_EMIT bottomPaddingChanged();
}

qreal WSurfaceItem::topPadding() const
{
    Q_D(const WSurfaceItem);
    return d->paddings.top();
}

void WSurfaceItem::setTopPadding(qreal newTopPadding)
{
    Q_D(WSurfaceItem);
    if (qFuzzyCompare(d->paddings.top(), newTopPadding))
        return;
    d->paddings.setTop(newTopPadding);
    d->onPaddingsChanged();
    setImplicitHeight(d->calculateImplicitHeight());
    Q_EMIT topPaddingChanged();
}

void WSurfaceItem::componentComplete()
{
    Q_D(WSurfaceItem);

    if (d->surface) {
        initSurface();
    }

    QQuickItem::componentComplete();
}

void WSurfaceItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    Q_D(WSurfaceItem);

    QQuickItem::geometryChange(newGeometry, oldGeometry);

    if (newGeometry.size() == oldGeometry.size())
        return;

    if (d->resizeMode == SizeToSurface) {
        if (d->effectiveVisible) {
            const QSizeF newSize = newGeometry.size();
            const QSizeF oldSize = oldGeometry.size();

            d->resizeSurfaceToItemSize(((newSize - d->paddingsSize()) * d->surfaceSizeRatio).toSize(),
                                       ((newSize - oldSize) * d->surfaceSizeRatio).toSize());
        }
    } else if (!d->surface && d->resizeMode != ManualResize) {
        if (d->contentContainer)
            d->contentContainer->setSize(d->contentContainer->size() +
                                         (newGeometry.size() - oldGeometry.size()) * d->surfaceSizeRatio);
    }

    d->updateBoundingRect();
}

void WSurfaceItem::itemChange(ItemChange change, const ItemChangeData &data)
{
    QQuickItem::itemChange(change, data);
    Q_D(WSurfaceItem);

    if (!d->componentComplete)
        return;

    if (change == ItemVisibleHasChanged) {
        if (d->surface) {
            if (d->effectiveVisible) {
                if (d->resizeMode != ManualResize)
                    d->doResize(d->resizeMode);
                d->contentContainer->setSize(d->surfaceState->contentSize);
                d->updateBoundingRect();
            }
        }

        Q_EMIT effectiveVisibleChanged();
    }
}

void WSurfaceItem::focusInEvent(QFocusEvent *event)
{
    QQuickItem::focusInEvent(event);

    // Q_D(WSurfaceItem);
    // if (d->eventItem)
    //     d->eventItem->forceActiveFocus(event->reason());
}

void WSurfaceItem::releaseResources()
{
    Q_D(WSurfaceItem);

    d->beforeRequestResizeSurfaceStateSeq = 0;

    // Detach from the wlr_surface signals before the wrapper is gone
    // (wlroots asserts empty listener lists on surface destroy).
    // Disconnect unconditionally: setSurface() clears d->surface before
    // calling releaseResources(), so the guard below would skip this and
    // leave the old wlr_surface's commit listener attached.
    if (d->surface && d->surfaceListenerOwner)
        d->surface->removeListeners(d->surfaceListenerOwner.get());
    if (d->surface) {
        d->surface->disconnect(this);
    }

    if (!d->surfaceFlags.testFlag(DontCacheLastBuffer)) {
        // Keep subsurface items alive as part of this item's cached last buffer.
        // They must leave d->subsurfaces, otherwise later bounding-rect updates may
        // treat cached/detached items as live subsurfaces. Once removed, the
        // queued auto-destroy handler below will skip deleteLater().
        const auto subsurfaces = d->subsurfaces;
        for (auto item : subsurfaces) {
            d->subsurfaces.removeOne(item);
            item->releaseResources();
            auto surface = item->surface();
            if (auto sub = surface ? wlr_subsurface_try_from_wlr_surface(surface->handle()) : nullptr) {
                d->subsurfaceDestroyListeners.erase(
                    std::remove_if(d->subsurfaceDestroyListeners.begin(), d->subsurfaceDestroyListeners.end(),
                        [sub](const auto &entry) { return entry.subsurface == sub; }),
                    d->subsurfaceDestroyListeners.end());
            }
        }
    } else {
        QList<WSurfaceItem*> tmp;
        tmp.swap(d->subsurfaces);
        for (auto item : std::as_const(tmp))
            item->deleteLater();
    }

    if (auto content = d->getItemContent())
        content->d_func()->invalidate();

    d->updateEventItem(true);
}

void WSurfaceItem::initSurface()
{
    Q_D(WSurfaceItem);

    d->initForDelegate();
    d->initForSurface();
}

bool WSurfaceItem::sendEvent(QInputEvent *event)
{
    Q_D(WSurfaceItem);
    if (!d->surface)
        return false;

    return WSeat::sendEvent(d->surface.get(), this, d->eventItem, event);
}

bool WSurfaceItem::doResizeSurface(const QSize &newSize)
{
    Q_D(WSurfaceItem);
    Q_ASSERT(d->shellSurface);
    d->shellSurface->resize(newSize);
    return true;
}

void WSurfaceItem::onSurfaceCommit()
{
    Q_D(WSurfaceItem);

    updateSurfaceState();

    // Maybe the beforeRequestResizeSurfaceStateSeq is set by resizeSurfaceToItemSize,
    // the resizeSurfaceToItemSize wants to resize the wl_surface to current size of WSurfaceitem,
    // If change the WSurfaceItem's size at here, you will see the WSurfaceItem flash.
    if (d->beforeRequestResizeSurfaceStateSeq < d->surface->handle()->current.seq) {
        if (d->beforeRequestResizeSurfaceStateSeq != 0) {
            Q_ASSERT(d->beforeRequestResizeSurfaceStateSeq == d->surface->handle()->current.seq - 1);
            d->beforeRequestResizeSurfaceStateSeq = 0;
        }

        if (d->effectiveVisible) {
            if (d->resizeMode == WSurfaceItem::SizeFromSurface)
                d->doResize(d->resizeMode);

            d->contentContainer->setSize(d->surfaceState->contentSize);
        }
        d->updateContentPosition();
    }

    d->updateSubsurfaceItem();
}

bool WSurfaceItem::resizeSurface(const QSizeF &newSize, bool tryExec)
{
    Q_D(const WSurfaceItem);
    if (!d->shellSurface || !d->contentContainer)
        return false;
    if (tryExec)
        return true;

    QRectF tmp(0, 0, newSize.width(), newSize.height());
    tmp -= d->paddings;
    // See surfaceSizeRatio, the content item maybe has been scaled.
    const QSize mappedSize = mapRectToItem(d->contentContainer, tmp).size().toSize();
    QSize clipedSize;
    if (!d->shellSurface->checkNewSize(mappedSize, &clipedSize))
        return doResizeSurface(clipedSize);
    else
        return doResizeSurface(mappedSize);
}

QRectF WSurfaceItem::getContentGeometry() const
{
    Q_D(const WSurfaceItem);
    return QRectF(QPointF(0, 0), d->surface->size());
}

QPointF WSurfaceItem::mapFromSurface(const QPointF &point) const
{
    const QPointF offset = getContentGeometry().topLeft();
    return QPointF(point.x() + leftPadding() - offset.x(),
                   point.y() + topPadding() - offset.y());
}

QSizeF WSurfaceItem::getContentSize() const
{
    Q_D(const WSurfaceItem);
    return d->surface->size();
}

bool WSurfaceItem::inputRegionContains(const QPointF &position) const
{
    Q_D(const WSurfaceItem);
    Q_ASSERT(d->surface);
    return d->surface->inputRegionContains(position);
}

void WSurfaceItem::surfaceSizeRatioChange()
{
    Q_D(WSurfaceItem);

    if (d->resizeMode != ManualResize)
        resize(d->resizeMode);

    if (d->contentContainer) {
        d->contentContainer->setTransformOrigin(QQuickItem::TopLeft);
        d->contentContainer->setScale(1.0 / d->surfaceSizeRatio);
    }

    if (d->surfaceState) {
        d->updateContentPosition();
    }

    if (d->eventItem) {
        d->updateEventItemGeometry();
    }

    if (d->surface) {
        d->updateSubsurfaceItem();
    }
}

void WSurfaceItem::updateSurfaceState()
{
    Q_D(WSurfaceItem);

    bool bufferScaleChanged = false;
    if (Q_LIKELY(d->surface)) {
        bufferScaleChanged = !qFuzzyCompare(d->surfaceState->bufferScale, d->surface->bufferScale());
        d->surfaceState->bufferScale = d->surface->bufferScale();
    }

    d->surfaceState->contentGeometry = getContentGeometry();
    d->surfaceState->contentSize = getContentSize();

    setImplicitSize(d->calculateImplicitWidth(),
                    d->calculateImplicitHeight());

    if (!d->ready && implicitWidth() > 0 && implicitHeight() > 0) {
        d->ready = true;
        Q_EMIT readyChanged();
    }

    if (bufferScaleChanged)
        Q_EMIT this->bufferScaleChanged();
}

WSurfaceItemPrivate::WSurfaceItemPrivate()
{
    smooth = true;
}

WSurfaceItemPrivate::~WSurfaceItemPrivate()
{

}

void WSurfaceItemPrivate::initForSurface()
{
    Q_Q(WSurfaceItem);

    // clean subsurfaces, if cacheLastBuffer is enabled will cache
    // the previous WSurface's subsurfaces.
    QList<WSurfaceItem*> tmp;
    tmp.swap(subsurfaces);
    for (auto item : std::as_const(tmp))
        item->deleteLater();

    if (!surfaceState)
        surfaceState.reset(new SurfaceState());

    QObject::connect(surface, &WSurface::beforeDestroy, q, &WSurfaceItem::releaseResources, Qt::DirectConnection);
    QObject::connect(surface, &WSurface::hasSubsurfaceChanged, q, [this]{ onHasSubsurfaceChanged(); });
    if (!surfaceListenerOwner)
        surfaceListenerOwner = std::make_unique<WListenerOwner>();
    surface->listeners(surfaceListenerOwner.get())->add(&surface->handle()->events.commit, q, &WSurfaceItem::onSurfaceCommit);

    onHasSubsurfaceChanged();
    updateEventItem(false);
    q->onSurfaceCommit();
}

void WSurfaceItemPrivate::initForDelegate()
{
    Q_Q(WSurfaceItem);

    std::unique_ptr<QQuickItem> newContentContainer;

    if (!delegate) {
        if (getItemContent()) {
            Q_ASSERT(!delegateIsDirty);
            return;
        }

        delegateIsDirty = false;
        auto contentItem = new WSurfaceItemContent(q);
        if (surface)
            contentItem->setSurface(surface);
        contentItem->setCacheLastBuffer(!surfaceFlags.testFlag(WSurfaceItem::DontCacheLastBuffer));
        contentItem->setSmooth(q->smooth());
        contentItem->setLive(!q->flags().testFlag(WSurfaceItem::NonLive));
        QObject::connect(q, &WSurfaceItem::smoothChanged, contentItem, &WSurfaceItemContent::setSmooth);
        newContentContainer.reset(contentItem);
    } else if (delegateIsDirty) {
        auto obj = delegate->createWithInitialProperties({{"surface", QVariant::fromValue(q)}}, qmlContext(q));
        if (!obj) {
            qCWarning(lcWlSurface) << "Failed on create surface item from delegate, error mssage:"
                       << delegate->errorString();
            return;
        }

        delegateIsDirty = false;
        auto contentItem = qobject_cast<QQuickItem*>(obj);
        if (!contentItem)
            qFatal() << "SurfaceItem's delegate must is Item";

        newContentContainer.reset(new QQuickItem(q));
        QQmlEngine::setObjectOwnership(contentItem, QQmlEngine::CppOwnership);
        contentItem->setParent(newContentContainer.get());
        contentItem->setParentItem(newContentContainer.get());
    }

    if (!newContentContainer)
        return;

    newContentContainer->setZ(qreal(WSurfaceItem::ZOrder::ContentItem));

    if (contentContainer) {
        newContentContainer->setPosition(contentContainer->position());
        newContentContainer->setSize(contentContainer->size());
        newContentContainer->setTransformOrigin(contentContainer->transformOrigin());
        newContentContainer->setScale(contentContainer->scale());

        contentContainer->disconnect(q);
        contentContainer->deleteLater();
    } else {
        newContentContainer->setTransformOrigin(QQuickItem::TransformOrigin::TopLeft);
        newContentContainer->setScale(1.0 / surfaceSizeRatio);
    }
    contentContainer = newContentContainer.release();

    updateEventItem(false);
    updateBoundingRect();
    if (eventItem)
        updateEventItemGeometry();

    Q_EMIT q->contentItemChanged();
}

void WSurfaceItemPrivate::onHasSubsurfaceChanged()
{
    auto *wlrSurface = surface->handle();
    Q_ASSERT(wlrSurface);
    if (surface->hasSubsurface())
        updateSubsurfaceItem();
}

void WSurfaceItemPrivate::updateSubsurfaceItem()
{
    auto surface = this->surface->handle();
    Q_ASSERT(surface);
    Q_ASSERT(contentContainer);
    updateSubsurfaceContainers();
    auto updateForContainer = [this](wl_list *subsurfaceList, QQuickItem *container) {
        wlr_subsurface *subsurface;
        QQuickItem *prev = nullptr;
        wl_list_for_each(subsurface, subsurfaceList, current.link) {
            auto qwSubsurface = subsurface;
            WSurface *surface = WSurface::fromHandle(subsurface->surface);
            if (!surface)
                continue;
            WSurfaceItem *item = ensureSubsurfaceItem(qwSubsurface, container);
            item->setSurfaceSizeRatio(surfaceSizeRatio);
            Q_ASSERT(item->parentItem() == container);
            if (prev) {
                Q_ASSERT(prev->parentItem() == item->parentItem());
                item->stackAfter(prev);
            }
            prev = item;
            const QPointF pos = contentContainer->position() + QPointF(subsurface->current.x, subsurface->current.y) / surfaceSizeRatio;
            item->setPosition(pos);
        }
    };
    updateForContainer(&surface->current.subsurfaces_below, belowSubsurfaceContainer);
    updateForContainer(&surface->current.subsurfaces_above, aboveSubsurfaceContainer);
    updateBoundingRect();
}

void WSurfaceItemPrivate::onPaddingsChanged()
{
    if (!surface || !surfaceState)
        return;

    if (resizeMode != WSurfaceItem::ManualResize && effectiveVisible)
        doResize(resizeMode);
    updateContentPosition();

    // subsurfae need contentContainer's position
    updateSubsurfaceItem();
}

void WSurfaceItemPrivate::updateContentPosition()
{
    Q_ASSERT(surfaceState);
    contentContainer->setPosition(-surfaceState->contentGeometry.topLeft() / surfaceSizeRatio
                                  + QPointF(paddings.left(), paddings.top()));
    updateBoundingRect();
}

WSurfaceItem *WSurfaceItemPrivate::ensureSubsurfaceItem(wlr_subsurface *subsurface, QQuickItem *parent)
{
    Q_Q(WSurfaceItem);

    Q_ASSERT(subsurface);
    WSurface *subsurfaceSurface = WSurface::fromHandle(subsurface->surface);
    Q_ASSERT(subsurfaceSurface);

    for (int i = 0; i < subsurfaces.count(); ++i) {
        auto surfaceItem = subsurfaces.at(i);
        WSurface *surface = surfaceItem->d_func()->surface.get();

        if (surface && surface == subsurfaceSurface) {
            if (surfaceItem->parentItem() == parent) {
                return surfaceItem;
            }

            // Moving between above/below containers emits removed on the old
            // container and added on the new one. Those signals keep
            // d->subsurfaces in sync, so preserve the item and only update its
            // QObject/visual parents.
            surfaceItem->setParent(parent);
            surfaceItem->setParentItem(parent);
            // Keep external observers' add/remove sequence balanced on container moves.
            Q_EMIT q->subsurfaceAdded(surfaceItem);
            return surfaceItem;
        }
    }

    auto surfaceItem = new WSurfaceItem(parent);
    // Delay destroy WSurfaceItem, because if the cause of destroy is because the parent
    // surface destroy, and the parent WSurfaceItem::cacheLastBuffer maybe enabled,
    // will disable this connection at parent WSurfaceItem::releaseResources to save the
    // parent WSurface last frame, the last frame contents should include its subsurfaces's
    // contents.
    QPointer<WSurfaceItem> surfaceItemGuard(surfaceItem);
    WSurfaceItemPrivate::SubsurfaceDestroyListener entry { subsurface, {} };
    entry.listener.init(&subsurface->events.destroy, q, [this, surfaceItemGuard, subsurface] (void *) {
            // Drop this listener entry first, before checking the guard:
            // wlr_subsurface destroy asserts the destroy listener list is
            // empty right after emitting, so the entry must go even when the
            // child item was already deleted. Self-erase is safe from inside
            // this callback (the closure outlives the emission).
            for (auto it = subsurfaceDestroyListeners.begin(); it != subsurfaceDestroyListeners.end(); ++it) {
                if (it->subsurface == subsurface) {
                    subsurfaceDestroyListeners.erase(it);
                    break;
                }
            }
            auto surfaceItem = surfaceItemGuard.data();
            if (!surfaceItem)
                return;
            // d->subsurfaces is the single live-subsurface registry for the parent. The
            // queued handler only owns deletion while the item is still in that registry:
            // if another path removed it first, it is either cached with the parent or
            // already scheduled for deletion.
            if (subsurfaces.removeOne(surfaceItem))
                surfaceItem->deleteLater();
        });
    subsurfaceDestroyListeners.push_back(std::move(entry));
    surfaceItem->setDelegate(delegate);
    surfaceItem->setFlags(surfaceFlags);
    surfaceItem->setSurface(subsurfaceSurface);
    surfaceItem->setSmooth(q->smooth());
    QObject::connect(q, &WSurfaceItem::smoothChanged, surfaceItem, &WSurfaceItem::setSmooth);
    QObject::connect(surfaceItem, &WSurfaceItem::boundingRectChanged, q, [this] {
        updateBoundingRect();
    });
    Q_EMIT q->subsurfaceAdded(surfaceItem);

    return surfaceItem;
}

void WSurfaceItemPrivate::connectSubsurfaceContainerSignals(SubsurfaceContainer *container)
{
    QObject::connect(container,
                     &SubsurfaceContainer::subsurfaceAdded,
                     q_func(),
                     [this](WSurfaceItem *item) {
                         // Container child add/remove is the synchronization point for
                         // the parent live-subsurface registry. New and reparented
                         // items enter d->subsurfaces through this path.
                         // Do not call updateBoundingRect() here: geometry/position for
                         // newly added subsurfaces is finalized in updateSubsurfaceItem().
                         // Do not emit q_func()->subsurfaceAdded() here: external add
                         // notifications are emitted from ensureSubsurfaceItem() only
                         // after the item is fully initialized.
                         if (subsurfaces.contains(item)) {
                             qCWarning(lcWlSurface)
                                 << "Duplicate subsurface add ignored:" << item;
                             return;
                         }
                         subsurfaces.append(item);
                     });
    QObject::connect(container,
                     &SubsurfaceContainer::subsurfaceRemoved,
                     q_func(),
                     [this](WSurfaceItem *item) {
                         // Container child removal is another lifetime path besides
                         // WSurface::destroyed; keep the parent registry in sync before
                         // recalculating geometry from d->subsurfaces.
                         subsurfaces.removeOne(item);
                         updateBoundingRect();
                         Q_EMIT q_func()->subsurfaceRemoved(item);
                     });
}

void WSurfaceItemPrivate::updateSubsurfaceContainers()
{
    Q_Q(WSurfaceItem);
    if (wl_list_empty(&surface->handle()->current.subsurfaces_below) && belowSubsurfaceContainer) {
        if (belowSubsurfaceContainer->isEmpty()) {
            delete belowSubsurfaceContainer;
        } else {
            belowSubsurfaceContainer->deleteAfterEmpty();
        }
    } else if (!wl_list_empty(&surface->handle()->current.subsurfaces_below) && !belowSubsurfaceContainer) {
        belowSubsurfaceContainer = new SubsurfaceContainer(q);
        belowSubsurfaceContainer->setZ(static_cast<qreal>(WSurfaceItem::ZOrder::BelowSubsurface));
        belowSubsurfaceContainer->setVisible(subsurfacesVisible);
        QQuickItemPrivate::get(belowSubsurfaceContainer)->anchors()->setFill(q);
        connectSubsurfaceContainerSignals(belowSubsurfaceContainer);
    }
    if (wl_list_empty(&surface->handle()->current.subsurfaces_above) && aboveSubsurfaceContainer) {
        if (aboveSubsurfaceContainer->isEmpty()) {
            delete aboveSubsurfaceContainer;
        } else {
            aboveSubsurfaceContainer->deleteAfterEmpty();
        }
    } else if (!wl_list_empty(&surface->handle()->current.subsurfaces_above)  && !aboveSubsurfaceContainer) {
        aboveSubsurfaceContainer = new SubsurfaceContainer(q);
        aboveSubsurfaceContainer->setZ(static_cast<qreal>(WSurfaceItem::ZOrder::AboveSubsurface));
        aboveSubsurfaceContainer->setVisible(subsurfacesVisible);
        QQuickItemPrivate::get(aboveSubsurfaceContainer)->anchors()->setFill(q);
        connectSubsurfaceContainerSignals(aboveSubsurfaceContainer);
    }
}

void WSurfaceItemPrivate::resizeSurfaceToItemSize(const QSize &itemSize, const QSize &sizeDiff)
{
    Q_Q(WSurfaceItem);

    Q_ASSERT_X(itemSize == ((q->size() - paddingsSize()) * surfaceSizeRatio).toSize(), "WSurfaceItem",
               "The function only using for reisze wl_surface's size to the WSurfaceItem's current size");

    if (!surface) {
        contentContainer->setSize(contentContainer->size() + sizeDiff);
        updateBoundingRect();
        return;
    }

    if (q->resizeSurface(itemSize)) {
        contentContainer->setSize(contentContainer->size() + sizeDiff);
        beforeRequestResizeSurfaceStateSeq = surface->handle()->pending.seq;
        updateBoundingRect();
    }
}

void WSurfaceItemPrivate::updateEventItem(bool forceDestroy)
{
    const bool needsEventItem = contentContainer && !forceDestroy
                                && !surfaceFlags.testFlag(WSurfaceItem::RejectEvent);
    if (bool(eventItem) == needsEventItem)
        return;

    if (auto eventItemTmp = eventItem) {
        // maybe trigger QQuickDeliveryAgentPrivate::clearFocusInScope in later,
        // first clear eventItem=nullptr to avoid forceActiveFocus on eventItem again.
        this->eventItem = nullptr;
        eventItemTmp->setVisible(false);
        eventItemTmp->setParent(nullptr);
        eventItemTmp->deleteLater();
    } else {
        eventItem = new EventItem(q_func());
        eventItem->setZ(qreal(WSurfaceItem::ZOrder::EventItem));
        eventItem->setFocus(true);
        updateEventItemGeometry();
    }

    Q_EMIT q_func()->eventItemChanged();
}

void WSurfaceItemPrivate::updateEventItemGeometry()
{
    Q_ASSERT(eventItem);
    Q_ASSERT(contentContainer);
    QQuickItemPrivate::get(eventItem)->anchors()->setFill(contentContainer);
    eventItem->setTransformOrigin(contentContainer->transformOrigin());
    eventItem->setScale(contentContainer->scale());
}

void WSurfaceItemPrivate::doResize(WSurfaceItem::ResizeMode mode)
{
    Q_ASSERT(mode != WSurfaceItem::ManualResize);
    Q_ASSERT(effectiveVisible);
    Q_ASSERT(surfaceState);
    Q_Q(WSurfaceItem);

    if (mode == WSurfaceItem::SizeFromSurface) {
        const QSizeF content = (surfaceState->contentGeometry.size() / surfaceSizeRatio) + paddingsSize();
        q->setSize(content);
    } else if (mode == WSurfaceItem::SizeToSurface) {
        const QSizeF newSize = (q->size() - paddingsSize()) * surfaceSizeRatio;
        const QSizeF oldSize = surfaceState->contentGeometry.size();

        resizeSurfaceToItemSize(newSize.toSize(), (newSize - oldSize).toSize());
    } else {
        qCWarning(lcWlSurface) << "Invalid resize mode" << mode;
    }
}

qreal WSurfaceItemPrivate::calculateImplicitWidth() const
{
    const auto ps = paddingsSize();
    if (!surfaceState)
        return ps.width();

    return surfaceState->contentGeometry.width() / surfaceSizeRatio + ps.width();
}

qreal WSurfaceItemPrivate::calculateImplicitHeight() const
{
    const auto ps = paddingsSize();
    if (!surfaceState)
        return ps.height();

    return surfaceState->contentGeometry.height() / surfaceSizeRatio + ps.height();
}

QRectF WSurfaceItemPrivate::calculateBoundingRect() const
{
    W_QC(WSurfaceItem);
    QRectF rect = QRectF(0, 0, q->width(), q->height());

    if (contentContainer)
        rect |= q->mapFromItem(contentContainer, contentContainer->boundingRect());

    for (auto sub : std::as_const(subsurfaces))
        rect |= sub->boundingRect().translated(sub->position());

    return rect;
}

void WSurfaceItemPrivate::updateBoundingRect()
{
    auto newBoundingRect = calculateBoundingRect();
    if (newBoundingRect == boundingRect)
        return;
    boundingRect = newBoundingRect;

    W_Q(WSurfaceItem);
    Q_EMIT q->boundingRectChanged();
}

WToplevelSurface *WSurfaceItem::shellSurface() const
{
    return d_func()->shellSurface;
}

bool WSurfaceItem::setShellSurface(WToplevelSurface *surface)
{
    Q_D(WSurfaceItem);
    if (d->shellSurface == surface)
        return false;

    if (d->shellSurface) {
        bool ok = d->shellSurface->disconnect(this);
        Q_ASSERT(ok);
    }
    d->shellSurface = surface;
    setSurface(surface ? surface->surface() : nullptr);
    Q_EMIT this->shellSurfaceChanged();
    return true;
}

void WSurfaceItemContentPrivate::cleanTextureProvider()
{
    if (textureProvider) {
        // needs check window, because maybe this item's window always is nullptr,
        // so not call WSurfaceItemContent::releaseResources before destroy.
        if (window) {
            class Q_DECL_HIDDEN WSurfaceItemContentCleanupJob : public QRunnable
            {
            public:
                WSurfaceItemContentCleanupJob(QObject *object) : m_object(object) { }
                void run() override {
                    delete m_object;
                }
                QObject *m_object;
            };

                   // Delay clean the textures on the next render after.
            window->scheduleRenderJob(new WSurfaceItemContentCleanupJob(textureProvider),
                                      QQuickWindow::AfterRenderingStage);
        } else {
            delete textureProvider;
        }

        textureProvider = nullptr;
    }
}

bool WSurfaceItem::subsurfacesVisible() const
{
    Q_D(const WSurfaceItem);
    return d->subsurfacesVisible;
}

void WSurfaceItem::setSubsurfacesVisible(bool newSubsurfacesVisible)
{
    Q_D(WSurfaceItem);
    if (d->subsurfacesVisible == newSubsurfacesVisible)
        return;
    d->subsurfacesVisible = newSubsurfacesVisible;
    if (d->belowSubsurfaceContainer)
        d->belowSubsurfaceContainer->setVisible(d->subsurfacesVisible);
    if (d->aboveSubsurfaceContainer)
        d->aboveSubsurfaceContainer->setVisible(d->subsurfacesVisible);
    Q_EMIT subsurfacesVisibleChanged();
}

bool WSurfaceItem::isReady() const
{
    Q_D(const WSurfaceItem);
    return d->ready;
}

WSurfaceItemContent *WSurfaceItem::findItemContent() const
{
    QQueue<QQuickItem *> q;
    q.enqueue(const_cast<WSurfaceItem*>(this));
    while (!q.empty()) {
        auto node = q.dequeue();
        if (auto content = qobject_cast<WSurfaceItemContent *>(node))
            return content;
        const auto children = node->childItems();
        for (auto child : children) {
            q.enqueue(child);
        }
    }
    return nullptr;
}

WAYLIB_SERVER_END_NAMESPACE

#include "wsurfaceitem.moc"
