// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0

#include "qmlobjectnode.h"
#include "abstractview.h"
#include "bindingproperty.h"
#include "nodeinstance.h"
#include "nodeinstanceview.h"
#include "nodelistproperty.h"
#include "nodemetainfo.h"
#include "nodeproperty.h"
#include "qml3dnode.h"
#include "qmlitemnode.h"
#include "qmlstate.h"
#include "qmltimelinekeyframegroup.h"
#include "qmlvisualnode.h"
#include "variantproperty.h"

#include <auxiliarydataproperties.h>
#include <invalidmodelnodeexception.h>

#include <qmltimeline.h>

#ifndef QMLDESIGNER_TEST
#include <qmldesignerplugin.h>
#endif

#include <utils/algorithm.h>
#include <utils/qtcassert.h>

#include <QRegularExpression>

namespace QmlDesigner {

void QmlObjectNode::setVariantProperty(const PropertyName &name, const QVariant &value)
{
    if (!isValid())
        return;

    if (timelineIsActive() && currentTimeline().isRecording()) {
        modelNode().validId();

        QmlTimelineKeyframeGroup timelineFrames(currentTimeline().keyframeGroup(modelNode(), name));

        Q_ASSERT(timelineFrames.isValid());

        qreal frame = currentTimeline().modelNode().auxiliaryDataWithDefault(currentFrameProperty).toReal();
        timelineFrames.setValue(value, frame);

        return;
    } else if (modelNode().hasId() && timelineIsActive()
               && currentTimeline().hasKeyframeGroup(modelNode(), name)) {
        QmlTimelineKeyframeGroup timelineFrames(currentTimeline().keyframeGroup(modelNode(), name));

        Q_ASSERT(timelineFrames.isValid());

        if (timelineFrames.isRecording()) {
            qreal frame = currentTimeline()
                              .modelNode()
                              .auxiliaryDataWithDefault(currentFrameProperty)
                              .toReal();
            timelineFrames.setValue(value, frame);

            return;
        }
    }

    if (isInBaseState()) {
        modelNode().variantProperty(name).setValue(value); //basestate
    } else {
        modelNode().validId();

        QmlPropertyChanges changeSet(currentState().propertyChanges(modelNode()));
        Q_ASSERT(changeSet.isValid());
        changeSet.modelNode().variantProperty(name).setValue(value);
    }
}

void QmlObjectNode::setBindingProperty(const PropertyName &name, const QString &expression)
{
    if (!isValid())
        return;

    if (isInBaseState()) {
        modelNode().bindingProperty(name).setExpression(expression); //basestate
    } else {
        modelNode().validId();

        QmlPropertyChanges changeSet(currentState().propertyChanges(modelNode()));
        Q_ASSERT(changeSet.isValid());
        changeSet.modelNode().bindingProperty(name).setExpression(expression);
    }
}

QmlModelState QmlObjectNode::currentState() const
{
    if (isValid())
        return QmlModelState(view()->currentStateNode());
    else
        return QmlModelState();
}

QmlTimeline QmlObjectNode::currentTimeline() const
{
    if (isValid())
        return view()->currentTimeline();
    else
        return QmlTimeline();
}

bool QmlObjectNode::isRootModelNode() const
{
    return modelNode().isRootNode();
}


/*!
    Returns the value of the property specified by \name that is based on an
    actual instance. The return value is not the value in the model, but the
    value of a real instantiated instance of this object.
*/
QVariant  QmlObjectNode::instanceValue(const PropertyName &name) const
{
    return nodeInstance().property(name);
}


bool QmlObjectNode::hasProperty(const PropertyName &name) const
{
    if (!isValid())
        return false;

    if (currentState().hasPropertyChanges(modelNode())) {
        QmlPropertyChanges propertyChanges = currentState().propertyChanges(modelNode());
        if (propertyChanges.modelNode().hasProperty(name))
            return true;
    }

    return modelNode().hasProperty(name);
}

bool QmlObjectNode::hasBindingProperty(const PropertyName &name) const
{
    if (!isValid())
        return false;

    if (currentState().hasPropertyChanges(modelNode())) {
        QmlPropertyChanges propertyChanges = currentState().propertyChanges(modelNode());
        if (propertyChanges.modelNode().hasBindingProperty(name))
            return true;
    }

    return modelNode().hasBindingProperty(name);
}

NodeAbstractProperty QmlObjectNode::nodeAbstractProperty(const PropertyName &name) const
{
    return modelNode().nodeAbstractProperty(name);
}

NodeAbstractProperty QmlObjectNode::defaultNodeAbstractProperty() const
{
    return modelNode().defaultNodeAbstractProperty();
}

NodeProperty QmlObjectNode::nodeProperty(const PropertyName &name) const
{
    return modelNode().nodeProperty(name);
}

NodeListProperty QmlObjectNode::nodeListProperty(const PropertyName &name) const
{
    return modelNode().nodeListProperty(name);
}

bool QmlObjectNode::instanceHasValue(const PropertyName &name) const
{
    return nodeInstance().hasProperty(name);
}

bool QmlObjectNode::propertyAffectedByCurrentState(const PropertyName &name) const
{
    if (!isValid())
        return false;

    if (currentState().isBaseState())
        return modelNode().hasProperty(name);

    if (timelineIsActive() && currentTimeline().hasTimeline(modelNode(), name))
        return true;

    if (!currentState().hasPropertyChanges(modelNode()))
        return false;

    return currentState().propertyChanges(modelNode()).modelNode().hasProperty(name);
}

QVariant QmlObjectNode::modelValue(const PropertyName &name) const
{
    if (!isValid())
        return false;

    if (timelineIsActive() && currentTimeline().hasTimeline(modelNode(), name)) {
        QmlTimelineKeyframeGroup timelineFrames(currentTimeline().keyframeGroup(modelNode(), name));

        Q_ASSERT(timelineFrames.isValid());

        qreal frame = currentTimeline().modelNode().auxiliaryDataWithDefault(currentFrameProperty).toReal();

        QVariant value = timelineFrames.value(frame);

        if (!value.isValid()) //interpolation is not done in the model
            value = instanceValue(name);

        return value;
    }

    if (currentState().isBaseState())
        return modelNode().variantProperty(name).value();

    if (!currentState().hasPropertyChanges(modelNode()))
        return modelNode().variantProperty(name).value();

    QmlPropertyChanges propertyChanges(currentState().propertyChanges(modelNode()));

    if (!propertyChanges.modelNode().hasProperty(name))
        return modelNode().variantProperty(name).value();

    return propertyChanges.modelNode().variantProperty(name).value();
}

bool QmlObjectNode::isTranslatableText(const PropertyName &name) const
{
    if (modelNode().metaInfo().isValid() && modelNode().metaInfo().hasProperty(name)
        && modelNode().metaInfo().property(name).propertyType().isString()) {
        if (modelNode().hasBindingProperty(name)) {
            static QRegularExpression regularExpressionPattern(
                QLatin1String("^qsTr(|Id|anslate)\\(\".*\"\\)$"));
            return modelNode().bindingProperty(name).expression().contains(regularExpressionPattern);
        }

        return false;
    }

    return false;
}

QString QmlObjectNode::stripedTranslatableText(const PropertyName &name) const
{
    if (modelNode().hasBindingProperty(name)) {
        static QRegularExpression regularExpressionPattern(
                    QLatin1String("^qsTr(|Id|anslate)\\(\"(.*)\"\\)$"));
        const QRegularExpressionMatch match = regularExpressionPattern.match(
                    modelNode().bindingProperty(name).expression());
        if (match.hasMatch())
            return match.captured(2);
        return instanceValue(name).toString();
    }
    return instanceValue(name).toString();
}

QString QmlObjectNode::expression(const PropertyName &name) const
{
    if (!isValid())
        return {};

    if (currentState().isBaseState())
        return modelNode().bindingProperty(name).expression();

    if (!currentState().hasPropertyChanges(modelNode()))
        return modelNode().bindingProperty(name).expression();

    QmlPropertyChanges propertyChanges(currentState().propertyChanges(modelNode()));

    if (!propertyChanges.modelNode().hasProperty(name))
        return modelNode().bindingProperty(name).expression();

    return propertyChanges.modelNode().bindingProperty(name).expression();
}

/*!
    Returns \c true if the ObjectNode is in the BaseState.
*/
bool QmlObjectNode::isInBaseState() const
{
    return currentState().isBaseState();
}

bool QmlObjectNode::timelineIsActive() const
{
    return currentTimeline().isValid();
}

bool QmlObjectNode::instanceCanReparent() const
{
    return isInBaseState();
}

QmlPropertyChanges QmlObjectNode::propertyChangeForCurrentState() const
{
    if (!isValid())
        return {};

    if (currentState().isBaseState())
        return QmlPropertyChanges();

    if (!currentState().hasPropertyChanges(modelNode()))
        return QmlPropertyChanges();

    return currentState().propertyChanges(modelNode());
}

static void removeStateOperationsForChildren(const QmlObjectNode &node)
{
    if (node.isValid()) {
        for (QmlModelStateOperation stateOperation : node.allAffectingStatesOperations()) {
            stateOperation.modelNode().destroy(); //remove of belonging StatesOperations
        }

        for (const QmlObjectNode childNode : node.modelNode().directSubModelNodes()) {
            removeStateOperationsForChildren(childNode);
        }
    }
}

static void removeAnimationsFromAnimation(const ModelNode &animation)
{
    QTC_ASSERT(animation.isValid(), return );

    auto model = animation.model();
    const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType(
        model->qtQuickPropertyAnimationMetaInfo());

    for (const ModelNode &child : propertyAnimations) {
        if (!child.hasBindingProperty("target")) {
            ModelNode nonConst = animation;
            nonConst.destroy();
            return;
        }
    }
}

static void removeAnimationsFromTransition(const ModelNode &transition, const QmlObjectNode &node)
{
    QTC_ASSERT(node.isValid(), return);
    QTC_ASSERT(transition.isValid(), return);

    const auto children = transition.directSubModelNodes();
    for (const ModelNode &parallel : children)
        removeAnimationsFromAnimation(parallel);
}

static void removeDanglingAnimationsFromTransitions(const QmlObjectNode &node)
{
    QTC_ASSERT(node.isValid(), return);

    auto root = node.view()->rootModelNode();

    if (root.isValid() && root.hasProperty("transitions")) {
        NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions");
        if (transitions.isValid()) {
            const auto transitionNodes = transitions.directSubNodes();
            for (const auto &transition : transitionNodes)
                removeAnimationsFromTransition(transition, node);
        }
    }
}

static void removeAliasExports(const QmlObjectNode &node)
{

    PropertyName propertyName = node.id().toUtf8();

    ModelNode rootNode = node.view()->rootModelNode();
    bool hasAliasExport = !propertyName.isEmpty()
            && rootNode.isValid()
            && rootNode.hasBindingProperty(propertyName)
            && rootNode.bindingProperty(propertyName).isAliasExport();

    if (hasAliasExport)
        rootNode.removeProperty(propertyName);


    const QList<ModelNode> nodes = node.modelNode().directSubModelNodes();
    for (const ModelNode &childNode : nodes) {
        removeAliasExports(childNode);
    }

}

static void removeLayerEnabled(const ModelNode &node)
{
    QTC_ASSERT(node.isValid(), return );
    if (node.parentProperty().isValid() && node.parentProperty().name() == "layer.effect") {
        ModelNode parent = node.parentProperty().parentModelNode();
        if (parent.isValid() && parent.hasProperty("layer.enabled"))
            parent.removeProperty("layer.enabled");
    }
}

static void deleteAllReferencesToNodeAndChildren(const ModelNode &node)
{
    BindingProperty::deleteAllReferencesTo(node);
    const auto subNodes = node.allSubModelNodes();
    for (const ModelNode &child : subNodes)
        BindingProperty::deleteAllReferencesTo(child);
}

/*!
    Deletes this object's node and its dependencies from the model.
    Everything that belongs to this Object, the ModelNode, and ChangeOperations
    is deleted from the model.
*/
void QmlObjectNode::destroy()
{
    if (!isValid())
        return;

    removeLayerEnabled(modelNode());
    removeAliasExports(modelNode());

    for (QmlModelStateOperation stateOperation : allAffectingStatesOperations()) {
        stateOperation.modelNode().destroy(); //remove of belonging StatesOperations
    }

    QVector<ModelNode> timelineNodes;
    const auto allNodes = view()->allModelNodes();
    for (const auto &timelineNode : allNodes) {
        if (QmlTimeline::isValidQmlTimeline(timelineNode))
            timelineNodes.append(timelineNode);
    }

    const auto subNodes = modelNode().allSubModelNodesAndThisNode();
    for (auto &timelineNode : std::as_const(timelineNodes)) {
        QmlTimeline timeline(timelineNode);
        for (const auto &subNode : subNodes)
            timeline.destroyKeyframesForTarget(subNode);
    }

    bool wasFlowEditorTarget = false;
    if (QmlFlowTargetNode::isFlowEditorTarget(modelNode())) {
        QmlFlowTargetNode(modelNode()).destroyTargets();
        wasFlowEditorTarget = true;
    }

    removeStateOperationsForChildren(modelNode());
    deleteAllReferencesToNodeAndChildren(modelNode());

    removeDanglingAnimationsFromTransitions(modelNode());

    QmlFlowViewNode root(view()->rootModelNode());

    modelNode().destroy();

    if (wasFlowEditorTarget && root.isValid())
        root.removeDanglingTransitions();
}

void QmlObjectNode::ensureAliasExport()
{
    if (!isValid())
        return;

    if (!isAliasExported()) {
        modelNode().validId();
        ModelNode rootModelNode = view()->rootModelNode();
        rootModelNode.bindingProperty(modelNode().id().toUtf8()).
            setDynamicTypeNameAndExpression("alias", modelNode().id());
    }
}

bool QmlObjectNode::isAliasExported() const
{

    if (modelNode().isValid() && !modelNode().id().isEmpty()) {
         PropertyName modelNodeId = modelNode().id().toUtf8();
         ModelNode rootModelNode = view()->rootModelNode();
         Q_ASSERT(rootModelNode.isValid());
         if (rootModelNode.hasBindingProperty(modelNodeId)
                 && rootModelNode.bindingProperty(modelNodeId).isDynamic()
                 && rootModelNode.bindingProperty(modelNodeId).expression() == modelNode().id())
             return true;
    }

    return false;
}

/*!
    Returns a list of states the affect this object.
*/

QList<QmlModelState> QmlObjectNode::allAffectingStates() const
{
    if (!isValid())
        return {};

    QList<QmlModelState> returnList;

    const QList<QmlModelState> states = allDefinedStates();
    for (const QmlModelState &state : states) {
        if (state.affectsModelNode(modelNode()))
            returnList.append(state);
    }
    return returnList;
}

/*!
    Returns a list of all state operations that affect this object.
*/

QList<QmlModelStateOperation> QmlObjectNode::allAffectingStatesOperations() const
{
    if (!isValid())
        return {};

    QList<QmlModelStateOperation> returnList;
    const QList<QmlModelState> states = allDefinedStates();
    for (const QmlModelState &state : states) {
        if (state.affectsModelNode(modelNode()))
            returnList.append(state.stateOperations(modelNode()));
    }

    return returnList;
}

static QList<QmlVisualNode> allQmlVisualNodesRecursive(const QmlItemNode &qmlItemNode)
{
    QList<QmlVisualNode> qmlVisualNodeList;

    if (qmlItemNode.isValid()) {
        qmlVisualNodeList.append(qmlItemNode);

        const QList<ModelNode> nodes = qmlItemNode.modelNode().directSubModelNodes();
        for (const ModelNode &modelNode : nodes) {
            if (QmlVisualNode::isValidQmlVisualNode(modelNode))
                qmlVisualNodeList.append(allQmlVisualNodesRecursive(modelNode));
        }
    }

    return qmlVisualNodeList;
}

QList<QmlModelState> QmlObjectNode::allDefinedStates() const
{
    if (!isValid())
        return {};

    QList<QmlModelState> returnList;

    QList<QmlVisualNode> allVisualNodes;

    if (QmlVisualNode::isValidQmlVisualNode(view()->rootModelNode()))
        allVisualNodes.append(allQmlVisualNodesRecursive(view()->rootModelNode()));

    for (const QmlVisualNode &node : std::as_const(allVisualNodes))
        returnList.append(node.states().allStates());

    const auto allNodes = view()->allModelNodes();
    for (const ModelNode &node : allNodes) {
        if (node.simplifiedTypeName() == "StateGroup")
            returnList.append(QmlModelStateGroup(node).allStates());
    }

    return returnList;
}

QList<QmlModelStateOperation> QmlObjectNode::allInvalidStateOperations() const
{
    QList<QmlModelStateOperation> result;

    const auto allStates =  allDefinedStates();
    for (const auto &state : allStates)
        result.append(state.allInvalidStateOperations());
    return result;
}

QmlModelStateGroup QmlObjectNode::states() const
{
    if (isValid())
        return QmlModelStateGroup(modelNode());
    else
        return QmlModelStateGroup();
}

QList<ModelNode> QmlObjectNode::allTimelines() const
{
    QList<ModelNode> timelineNodes;
    const auto allNodes = view()->allModelNodes();
    for (const auto &timelineNode : allNodes) {
        if (QmlTimeline::isValidQmlTimeline(timelineNode))
            timelineNodes.append(timelineNode);
    }

    return timelineNodes;
}

QList<ModelNode> QmlObjectNode::getAllConnections() const
{
    if (!isValid())
        return {};

    auto list = view()->allModelNodesOfType(model()->qtQuickConnectionsMetaInfo());
    return Utils::filtered(list, [this](const ModelNode &connection) {
        return connection.hasBindingProperty("target")
               && connection.bindingProperty("target").resolveToModelNode() == modelNode();
    });
}

/*!
    Removes a variant property of the object specified by \a name from the
    model.
*/

void  QmlObjectNode::removeProperty(const PropertyName &name)
{
    if (!isValid())
        return;

    if (isInBaseState()) {
        modelNode().removeProperty(name); //basestate
    } else {
        QmlPropertyChanges changeSet(currentState().propertyChanges(modelNode()));
        Q_ASSERT(changeSet.isValid());
        changeSet.removeProperty(name);
    }
}

QList<ModelNode> toModelNodeList(const QList<QmlObjectNode> &qmlObjectNodeList)
{
    QList<ModelNode> modelNodeList;

    for (const QmlObjectNode &qmlObjectNode : qmlObjectNodeList)
        modelNodeList.append(qmlObjectNode.modelNode());

    return modelNodeList;
}

QList<QmlObjectNode> toQmlObjectNodeList(const QList<ModelNode> &modelNodeList)
{
    QList<QmlObjectNode> qmlObjectNodeList;

    for (const ModelNode &modelNode : modelNodeList) {
        if (QmlObjectNode::isValidQmlObjectNode(modelNode))
             qmlObjectNodeList.append(modelNode);
    }

    return qmlObjectNodeList;
}

bool QmlObjectNode::isAncestorOf(const QmlObjectNode &objectNode) const
{
    return modelNode().isAncestorOf(objectNode.modelNode());
}

QVariant QmlObjectNode::instanceValue(const ModelNode &modelNode, const PropertyName &name)
{
    Q_ASSERT(modelNode.view()->nodeInstanceView()->hasInstanceForModelNode(modelNode));
    return modelNode.view()->nodeInstanceView()->instanceForModelNode(modelNode).property(name);
}

QString QmlObjectNode::generateTranslatableText([[maybe_unused]] const QString &text)
{
#ifndef QMLDESIGNER_TEST

    if (QmlDesignerPlugin::settings().value(
            DesignerSettingsKey::TYPE_OF_QSTR_FUNCTION).toInt())

        switch (QmlDesignerPlugin::settings().value(
                    DesignerSettingsKey::TYPE_OF_QSTR_FUNCTION).toInt()) {
        case 0: return QString(QStringLiteral("qsTr(\"%1\")")).arg(text);
        case 1: return QString(QStringLiteral("qsTrId(\"%1\")")).arg(text);
        case 2: return QString(QStringLiteral("qsTranslate(\"%1\", \"context\")")).arg(text);
        default:
            break;

        }
    return QString(QStringLiteral("qsTr(\"%1\")")).arg(text);
#else
    return QString();
#endif
}

QString QmlObjectNode::stripedTranslatableTextFunction(const QString &text)
{
    const QRegularExpression regularExpressionPattern(
                QLatin1String("^qsTr(|Id|anslate)\\(\"(.*)\"\\)$"));
    const QRegularExpressionMatch match = regularExpressionPattern.match(text);
    if (match.hasMatch())
        return match.captured(2);
    return text;
}

QString QmlObjectNode::convertToCorrectTranslatableFunction(const QString &text)
{
    return generateTranslatableText(stripedTranslatableTextFunction(text));
}

TypeName QmlObjectNode::instanceType(const PropertyName &name) const
{
    return nodeInstance().instanceType(name);
}

bool QmlObjectNode::instanceHasBinding(const PropertyName &name) const
{
    return nodeInstance().hasBindingForProperty(name);
}

NodeInstance QmlObjectNode::nodeInstance() const
{
    return nodeInstanceView()->instanceForModelNode(modelNode());
}

QmlObjectNode QmlObjectNode::nodeForInstance(const NodeInstance &instance) const
{
    return QmlObjectNode(ModelNode(instance.modelNode(), view()));
}

QmlItemNode QmlObjectNode::itemForInstance(const NodeInstance &instance) const
{
    return QmlItemNode(ModelNode(instance.modelNode(), view()));
}

bool QmlObjectNode::isValidQmlObjectNode(const ModelNode &modelNode)
{
    return isValidQmlModelNodeFacade(modelNode);
}

bool QmlObjectNode::isValid() const
{
    return isValidQmlObjectNode(modelNode());
}

bool QmlObjectNode::hasError() const
{
    if (isValid())
        return nodeInstance().hasError();
    else
        qDebug() << "called hasError() on an invalid QmlObjectNode";
    return false;
}

QString QmlObjectNode::error() const
{
    if (hasError())
        return nodeInstance().error();
    return QString();
}

bool QmlObjectNode::hasNodeParent() const
{
    return modelNode().hasParentProperty();
}

bool QmlObjectNode::hasInstanceParent() const
{
    return nodeInstance().parentId() >= 0 && nodeInstanceView()->hasInstanceForId(nodeInstance().parentId());
}

bool QmlObjectNode::hasInstanceParentItem() const
{
    return isValid()
           && nodeInstance().parentId() >= 0
           && nodeInstanceView()->hasInstanceForId(nodeInstance().parentId())
           && QmlItemNode::isItemOrWindow(view()->modelNodeForInternalId(nodeInstance().parentId()));
}


void QmlObjectNode::setParentProperty(const NodeAbstractProperty &parentProeprty)
{
    return modelNode().setParentProperty(parentProeprty);
}

QmlObjectNode QmlObjectNode::instanceParent() const
{
    if (hasInstanceParent())
        return nodeForInstance(nodeInstanceView()->instanceForId(nodeInstance().parentId()));

    return QmlObjectNode();
}

QmlItemNode QmlObjectNode::instanceParentItem() const
{
    if (hasInstanceParentItem())
        return itemForInstance(nodeInstanceView()->instanceForId(nodeInstance().parentId()));

    return QmlItemNode();
}

QmlItemNode QmlObjectNode::modelParentItem() const
{
    return modelNode().parentProperty().parentModelNode();
}

void QmlObjectNode::setId(const QString &id)
{
    modelNode().setIdWithRefactoring(id);
}

QString QmlObjectNode::id() const
{
    return modelNode().id();
}

QString QmlObjectNode::validId()
{
    return modelNode().validId();
}

bool QmlObjectNode::hasDefaultPropertyName() const
{
    return modelNode().metaInfo().hasDefaultProperty();
}

PropertyName QmlObjectNode::defaultPropertyName() const
{
    return modelNode().metaInfo().defaultPropertyName();
}

void QmlObjectNode::setParent(const QmlObjectNode &newParent)
{
    if (newParent.hasDefaultPropertyName())
        newParent.modelNode().defaultNodeAbstractProperty().reparentHere(modelNode());
}

QmlItemNode QmlObjectNode::toQmlItemNode() const
{
    return QmlItemNode(modelNode());
}

QmlVisualNode QmlObjectNode::toQmlVisualNode() const
{
     return QmlVisualNode(modelNode());
}

QString QmlObjectNode::simplifiedTypeName() const
{
    return modelNode().simplifiedTypeName();
}

QStringList QmlObjectNode::allStateNames() const
{
    return nodeInstance().allStateNames();
}

QmlObjectNode *QmlObjectNode::getQmlObjectNodeOfCorrectType(const ModelNode &modelNode)
{
    // Create QmlObjectNode of correct type for the modelNode
    // Note: Currently we are only interested in differentiating 3D nodes, so no check for
    // visual nodes is done for efficiency reasons
    if (modelNode.isValid() && modelNode.metaInfo().isQtQuick3DNode())
        return new Qml3DNode(modelNode);
    return new QmlObjectNode(modelNode);
}

bool QmlObjectNode::isBlocked([[maybe_unused]] const PropertyName &propName) const
{
    return false;
}

} //QmlDesigner
