// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtQuick/private/qquickitem_p.h>

#include "qqstylekitdelegatecontainer_p.h"
#include "../qqstylekitcontrolproperties_p.h"

QT_BEGIN_NAMESPACE

QQmlComponent* QQStyleKitDelegateContainer::s_defaultDelegateComponent = nullptr;
QQmlComponent* QQStyleKitDelegateContainer::s_defaultShadowComponent = nullptr;

QQStyleKitDelegateContainer::QQStyleKitDelegateContainer(QQuickItem *parent)
    : QQuickItem(parent)
{
}

QQStyleKitDelegateContainer::~QQStyleKitDelegateContainer()
{
    // Disconnect before QQuickItem::~QQuickItem() reparents children, which emits
    // signals. At that point our vtable would already be replaced with QQuickItem's,
    // causing assertObjectType to fail.
    if (m_delegateInstance)
        disconnect(m_delegateInstance, nullptr, this, nullptr);
}

QQStyleKitDelegateProperties *QQStyleKitDelegateContainer::delegateStyle() const
{
    return m_delegateProperties;
}

void QQStyleKitDelegateContainer::setDelegateStyle(QQStyleKitDelegateProperties *delegateProperties)
{
    if (m_delegateProperties == delegateProperties)
        return;

    if (m_delegateProperties) {
        /* We don't expect m_delegateProperties to change after componentCompleted(), since it's bound
         * to a controls StyleKitReader, which is not supposed to change. So, considering that there
         * might be hundreds of delegate instances in an application, we try to save some connections
         * this way. But note, this is only an optimization, not a technical limitation. */
        qmlWarning(this) << "Changing delegateStyle on StyleKitContainer is not supported.";
        return;
    }

    m_delegateProperties = delegateProperties;
    emit delegateStyleChanged();
}

QObject *QQStyleKitDelegateContainer::quickControl() const
{
    return m_control;
}

void QQStyleKitDelegateContainer::setQuickControl(QObject *control)
{
    if (m_control == control)
        return;
    m_control = control;
    emit quickControlChanged();
}

QQuickItem *QQStyleKitDelegateContainer::delegateInstance() const
{
    return m_delegateInstance;
}

void QQStyleKitDelegateContainer::updateImplicitSize()
{
    if (m_inGeometryChange) {
        /* The delegate instance is expected (but not restricted) to bind its size to the parent
         * container since the final size of the control (and its delegates) is set from the
         * application. However, some delegates also change their implicit size when resized.
         * Shape, for example, recomputes its implicit size from the bounding rect of its
         * ShapePaths, which may in turn depend on the Shape's own size. And a change to the
         * implicit size can cause the layout that the control is in to relayout (and resize)
         * the control once more, which causes a binding loop. To prevent this, we ignore changes
         * to the delegate's implicit size while we're resizing it. */
        return;
    }

    qreal implicitWidth = 0;
    qreal implicitHeight = 0;

    if (m_delegateInstance) {
        implicitWidth = m_delegateInstance->implicitWidth();
        implicitHeight = m_delegateInstance->implicitHeight();
    } else if (m_delegateProperties) {
        /* The delegate instance may not exist yet (e.g. because it is hidden),
         * but the container should still report the style's implicit size so
         * that it reserves the correct space in a layout. */
        implicitWidth = qMax(m_delegateProperties->minimumWidth(), m_delegateProperties->width());
        implicitHeight = qMax(m_delegateProperties->minimumHeight(), m_delegateProperties->height());
    }

    setImplicitWidth(implicitWidth);
    setImplicitHeight(implicitHeight);
}

void QQStyleKitDelegateContainer::maybeCreateDelegate()
{
    if (m_delegateInstance)
        return;
    if (!m_delegateProperties)
        return;

    if (!m_delegateProperties->visible()) {
        connect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
                this, &QQStyleKitDelegateContainer::maybeCreateDelegate, Qt::UniqueConnection);
        return;
    }

    disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
               this, &QQStyleKitDelegateContainer::maybeCreateDelegate);

    if (QQmlComponent *delegateComponent = m_delegateProperties->delegate()) {
        m_delegateComponent = delegateComponent;
    } else {
        if (!s_defaultDelegateComponent || s_defaultDelegateComponent->engine() != qmlEngine(this)) {
            delete s_defaultDelegateComponent;
            QQmlEngine *engine = qmlEngine(this);
            s_defaultDelegateComponent = new QQmlComponent(engine);
            const QString qmlCode = QString::fromUtf8(R"(
                import Qt.labs.StyleKit
                StyledItem {
                    visible: delegateStyle.visible
                    width: parent.width
                    height: parent.height
                }
            )");
            s_defaultDelegateComponent->setData(qmlCode.toUtf8(), QUrl());
            Q_ASSERT_X(!s_defaultDelegateComponent->isError(), __FUNCTION__,
                       s_defaultDelegateComponent->errorString().toUtf8().constData());
        }
        m_delegateComponent = s_defaultDelegateComponent;
    }

    QQmlContext *ctx = QQmlEngine::contextForObject(this);
    m_delegateInstance = qobject_cast<QQuickItem*>(m_delegateComponent->beginCreate(ctx));
    Q_ASSERT(m_delegateInstance);
    m_delegateInstance->setParent(this);
    m_delegateInstance->setParentItem(this);
    m_delegateInstance->setProperty("control", QVariant::fromValue(m_control.get()));
    m_delegateInstance->setProperty("delegateStyle", QVariant::fromValue(m_delegateProperties.get()));
    m_delegateComponent->completeCreate();

    updateImplicitSize();
    connect(m_delegateInstance, &QQuickItem::implicitWidthChanged, this, &QQStyleKitDelegateContainer::updateImplicitSize);
    connect(m_delegateInstance, &QQuickItem::implicitHeightChanged, this, &QQStyleKitDelegateContainer::updateImplicitSize);

}

void QQStyleKitDelegateContainer::maybeCreateShadow()
{
    if (m_shadowInstance)
        return;
    if (!m_delegateProperties)
        return;

    if (!m_delegateProperties->visible()) {
        connect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
                this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
        return;
    }
    if (!m_delegateProperties->shadow()->visible()) {
        connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::visibleChanged,
                this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
        return;
    }
    if (m_delegateProperties->shadow()->color().alpha() == 0) {
        connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::colorChanged,
                this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
        return;
    }
    if (m_delegateProperties->shadow()->opacity() == 0) {
        connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::opacityChanged,
                this, &QQStyleKitDelegateContainer::maybeCreateShadow, Qt::UniqueConnection);
        return;
    }

    disconnect(m_delegateProperties, &QQStyleKitDelegateProperties::visibleChanged,
            this, &QQStyleKitDelegateContainer::maybeCreateShadow);
    disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::visibleChanged,
            this, &QQStyleKitDelegateContainer::maybeCreateShadow);
    disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::colorChanged,
            this, &QQStyleKitDelegateContainer::maybeCreateShadow);
    disconnect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::opacityChanged,
            this, &QQStyleKitDelegateContainer::maybeCreateShadow);

    if (QQmlComponent *shadowComponent = m_delegateProperties->shadow()->delegate()) {
        m_shadowComponent = shadowComponent;
    } else {
        if (!s_defaultShadowComponent || s_defaultShadowComponent->engine() != qmlEngine(this)) {
            delete s_defaultShadowComponent;
            QQmlEngine *engine = qmlEngine(this);
            s_defaultShadowComponent = new QQmlComponent(engine);
            const QString qmlCode = QString::fromUtf8(R"(
                import Qt.labs.StyleKit.impl
                Shadow {}
            )");
            s_defaultShadowComponent->setData(qmlCode.toUtf8(), QUrl());
            Q_ASSERT_X(!s_defaultShadowComponent->isError(), __FUNCTION__,
                       s_defaultShadowComponent->errorString().toUtf8().constData());
        }
        m_shadowComponent = s_defaultShadowComponent;
    }

    QQmlContext *ctx = QQmlEngine::contextForObject(this);
    m_shadowInstance = qobject_cast<QQuickItem*>(m_shadowComponent->beginCreate(ctx));
    Q_ASSERT(m_shadowInstance);
    m_shadowInstance->setParent(this);
    m_shadowInstance->setParentItem(this);
    m_shadowInstance->setZ(-1);
    m_shadowInstance->setProperty("control", QVariant::fromValue(m_control.get()));
    m_shadowInstance->setProperty("delegateStyle", QVariant::fromValue(m_delegateProperties.get()));
    m_shadowComponent->completeCreate();
}

void QQStyleKitDelegateContainer::componentComplete()
{
    QQuickItem::componentComplete();
    Q_ASSERT(m_delegateProperties);
    Q_ASSERT(m_control);

    maybeCreateDelegate();
    connect(m_delegateProperties, &QQStyleKitDelegateProperties::delegateChanged, this, [this]{
        if (!m_delegateInstance) {
            maybeCreateDelegate();
        } else {
            const QQmlComponent *newDelegateComp = m_delegateProperties->delegate();
            if (m_delegateComponent == newDelegateComp)
                return;
            if (!newDelegateComp && m_delegateComponent == s_defaultDelegateComponent) {
                /* If newDelegateComp is nullptr, it means that we should use the default
                 * delegate instead, which we already do. */
                return;
            }

            delete m_delegateInstance;
            maybeCreateDelegate();
            emit delegateInstanceChanged();
        }
    });

    maybeCreateShadow();
    connect(m_delegateProperties->shadow(), &QQStyleKitShadowProperties::delegateChanged, this, [this]{
        if (!m_shadowInstance) {
            maybeCreateShadow();
        } else {
            const QQmlComponent *newShadowComp = m_delegateProperties->shadow()->delegate();
            if (m_shadowComponent == newShadowComp)
                return;
            if (!newShadowComp && m_shadowComponent == s_defaultShadowComponent) {
                /* If newShadowComp is nullptr, it means that we should use the default
                 * delegate instead, which we already do. */
                return;
            }

            delete m_shadowInstance;
            maybeCreateShadow();
        }
    });

    /* rotation and scale are applied here rather than as QML bindings
     * in each QML file that uses a container, simply to avoid repeating the same
     * bindings in all container instances.  But note, the properties we set from
     * c++ cannot then be bound to another value from QML since the c++ code would
     * ignore and overwrite such bindings. For this reason, properties like 'visible'
     * still needs to be set from QML since a container should sometimes be hidden
     * based on the state of the control (e.g a menu arrow should only be visible
     * when the menu has children etc). */
    setRotation(m_delegateProperties->rotation());
    setScale(m_delegateProperties->scale());

    connect(m_delegateProperties, &QQStyleKitDelegateProperties::rotationChanged, this, [this]() {
        setRotation(m_delegateProperties->rotation());
    });
    connect(m_delegateProperties, &QQStyleKitDelegateProperties::scaleChanged, this, [this]() {
        setScale(m_delegateProperties->scale());
    });

    updateImplicitSize();
}

void QQStyleKitDelegateContainer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QScopedValueRollback implicitSizeUpdateGuard(m_inGeometryChange, true);
    QQuickItem::geometryChange(newGeometry, oldGeometry);
}

QT_END_NAMESPACE

#include "moc_qqstylekitdelegatecontainer_p.cpp"
