// SPDX-License-Identifier: LGPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea.
*
* nymea is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* nymea is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "jsonhandler.h"
#include "typeutils.h"
#include "loggingcategories.h"

#include <QDebug>
#include <QDateTime>

#include "types/param.h"

JsonHandler::JsonHandler(QObject *parent) : QObject(parent)
{
    qRegisterMetaType<QMetaType::Type>();
    registerEnum<BasicType>();
}

QHash<QString, QString> JsonHandler::cacheHashes() const
{
    return QHash<QString, QString>();
}

QVariantMap JsonHandler::translateNotification(const QString &notification, const QVariantMap &params, const QLocale &locale)
{
    Q_UNUSED(notification)
    Q_UNUSED(locale)
    return params;
}

QVariantMap JsonHandler::jsonEnums() const
{
    return m_enums;
}

QVariantMap JsonHandler::jsonFlags() const
{
    return m_flags;
}

QVariantMap JsonHandler::jsonObjects() const
{
    return m_objects;
}

QVariantMap JsonHandler::jsonMethods() const
{
    return m_methods;
}

QVariantMap JsonHandler::jsonNotifications() const
{
    return m_notifications;
}

QString JsonHandler::objectRef(const QString &objectName)
{
    return "$ref:" + objectName;
}

JsonHandler::BasicType JsonHandler::variantTypeToBasicType(QMetaType::Type variantType)
{
    switch (variantType) {
    case QMetaType::QUuid:
        return Uuid;
    case QMetaType::QString:
        return String;
    case QMetaType::QStringList:
        return StringList;
    case QMetaType::Int:
        return Int;
    case QMetaType::UInt:
        return Uint;
    case QMetaType::Double:
        return Double;
    case QMetaType::Bool:
        return Bool;
    case QMetaType::QColor:
        return Color;
    case QMetaType::QTime:
        return Time;
    case QMetaType::QVariantMap:
        return Object;
    case QMetaType::QDateTime:
        return Uint; // DateTime is represented as time_t
    default:
        return Variant;
    }
}

QMetaType::Type JsonHandler::basicTypeToMetaType(JsonHandler::BasicType basicType)
{
    switch (basicType) {
    case Uuid:
        return QMetaType::QUuid;
    case String:
        return QMetaType::QString;
    case StringList:
        return QMetaType::QStringList;
    case Int:
        return QMetaType::Int;
    case Uint:
        return QMetaType::UInt;
    case Double:
        return QMetaType::Double;
    case Bool:
        return QMetaType::Bool;
    case Color:
        return QMetaType::QColor;
    case Time:
        return QMetaType::QTime;
    case Object:
        return QMetaType::QVariantMap;
    case Variant:
        return QMetaType::UnknownType;
    }
    return QMetaType::UnknownType;
}

void JsonHandler::registerObject(const QString &name, const QVariantMap &object)
{
    m_objects.insert(name, object);
}

void JsonHandler::registerMethod(const QString &name, const QString &description, const QVariantMap &params, const QVariantMap &returns, Types::PermissionScope permissionScope, const QString &deprecationInfo)
{
    QVariantMap methodData;
    methodData.insert("description", description);
    methodData.insert("params", params);
    methodData.insert("returns", returns);
    methodData.insert("permissionScope", enumValueName(permissionScope));
    if (!deprecationInfo.isEmpty()) {
        methodData.insert("deprecated", deprecationInfo);
    }

    m_methods.insert(name, methodData);
}

void JsonHandler::registerNotification(const QString &name, const QString &description, const QVariantMap &params, const QString &deprecationInfo)
{
    QVariantMap notificationData;
    notificationData.insert("description", description);
    notificationData.insert("params", params);
    if (!deprecationInfo.isEmpty()) {
        notificationData.insert("deprecated", deprecationInfo);
    }

    m_notifications.insert(name, notificationData);
}

JsonReply *JsonHandler::createReply(const QVariantMap &data) const
{
    return JsonReply::createReply(const_cast<JsonHandler*>(this), data);
}

JsonReply *JsonHandler::createAsyncReply(const QString &method) const
{
    return JsonReply::createAsyncReply(const_cast<JsonHandler*>(this), method);
}

void JsonHandler::registerObject(const QMetaObject &metaObject)
{
    QString className = QString(metaObject.className()).split("::").last();
    QVariantMap description;
    for (int i = 0; i < metaObject.propertyCount(); i++) {
        QMetaProperty metaProperty = metaObject.property(i);
        QString name = metaProperty.name();

        if (name == "objectName") {
            continue; // Skip QObject's objectName property
        }
        if (metaProperty.isUser()) {
            name.prepend("o:");
        }
        if (!metaProperty.isWritable()) {
            name.prepend("r:");
        }
        if (metaProperty.revision() != 0) {
            name.prepend("d:");
        }
        QVariant typeName;
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
        int typeId = metaProperty.typeId();
        if (typeId >= QMetaType::User) {
            if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) {
                typeName = QString("$ref:BasicType");
            } else if (QString(metaProperty.typeName()).startsWith("QList")) {
                QString elementType = QString(metaProperty.typeName()).remove("QList<").remove(">");
                if (elementType == "ThingId" || elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") {
                    elementType = "QUuid";
                }
                QMetaType::Type variantType = static_cast<QMetaType::Type>(QMetaType::fromName(elementType.toUtf8()).id());
                typeName = QVariantList() << enumValueName(variantTypeToBasicType(variantType));
            } else {
                QString typeNameRaw = QString(metaProperty.typeName());
                // QString propertyNameRaw = QString(metaProperty.name());
                // QString metaTypeNameRaw = QString(metaProperty.metaType().name());
                if (typeNameRaw.contains("QFlag")) {
                    QString enumType = QString(typeNameRaw).split("::").last().remove('<').remove('>');
                    typeName = QString("$ref:%1").arg(m_flagsEnums.key(enumType));
                } else {
                    typeName = QString("$ref:%1").arg(QString(typeNameRaw).split("::").last().remove('<').remove('>'));
                }

                // qCDebug(dcJsonRpc()) << typeNameRaw << propertyNameRaw << metaTypeNameRaw << typeName;
            }
        } else if (metaProperty.isEnumType()) {
            QString typeNameRaw = QString(metaProperty.typeName());
            // Note: since Qt6 flags are also enums, and the type name contains
            // QFlags<Class:Enum>, therefore we need to remove additionally all < >
            typeName = QString("$ref:%1").arg(typeNameRaw.split("::").last().remove('<').remove('>'));
        } else if (metaProperty.isFlagType()) {
            QString flagType = QString(metaProperty.typeName()).split("::").last().remove('<').remove('>');
            typeName = QVariantList() << "$ref:" + m_flagsEnums.value(flagType, flagType);
        } else if (typeId == QMetaType::QVariantList) {
            typeName = QVariantList() << enumValueName(Variant);
        } else {
            typeName = enumValueName(variantTypeToBasicType(static_cast<QMetaType::Type>(typeId)));
        }
#else
        if (metaProperty.type() == QVariant::UserType) {
            if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) {
                typeName = QString("$ref:BasicType");
            } else if (QString(metaProperty.typeName()).startsWith("QList")) {
                QString elementType = QString(metaProperty.typeName()).remove("QList<").remove(">");
                if (elementType == "ThingId" || elementType == "EventTypeId" || elementType == "StateTypeId" || elementType == "ActionTypeId") {
                    elementType = "QUuid";
                }
                QMetaType::Type variantType = static_cast<QMetaType::Type>(QVariant::nameToType(elementType.toUtf8()));
                typeName = QVariantList() << enumValueName(variantTypeToBasicType(variantType));
            } else {
                typeName = QString("$ref:%1").arg(QString(metaProperty.typeName()).split("::").last());
            }
        } else if (metaProperty.isEnumType()) {
            typeName = QString("$ref:%1").arg(QString(metaProperty.typeName()).split("::").last());
        } else if (metaProperty.isFlagType()) {
            QString flagType = QString(metaProperty.typeName()).split("::").last();
            typeName = QVariantList() << "$ref:" + m_flagsEnums.value(flagType, flagType);
        } else if (metaProperty.type() == QVariant::List) {
            typeName = QVariantList() << enumValueName(Variant);
        } else {
            typeName = enumValueName(variantTypeToBasicType(static_cast<QMetaType::Type>(metaProperty.type())));
        }

#endif
        description.insert(name, typeName);
    }
    m_objects.insert(className, description);
    m_metaObjects.insert(className, metaObject);
}

void JsonHandler::registerList(const QMetaObject &listMetaObject, const QMetaObject &metaObject)
{
    QString listTypeName = QString(listMetaObject.className()).split("::").last();
    QString objectTypeName = QString(metaObject.className()).split("::").last();
    m_objects.insert(listTypeName, QVariantList() << QVariant(QString("$ref:%1").arg(objectTypeName)));
    m_metaObjects.insert(listTypeName, listMetaObject);
    m_listMetaObjects.insert(listTypeName, listMetaObject);
    m_listEntryTypes.insert(listTypeName, objectTypeName);
    Q_ASSERT_X(listMetaObject.indexOfProperty("count") >= 0, "JsonHandler", QString("List type %1 does not implement \"count\" property!").arg(listTypeName).toUtf8());
    Q_ASSERT_X(listMetaObject.indexOfMethod("get(int)") >= 0, "JsonHandler", QString("List type %1 does not implement \"Q_INVOKABLE QVariant get(int index)\" method!").arg(listTypeName).toUtf8());
    Q_ASSERT_X(listMetaObject.indexOfMethod("put(QVariant)") >= 0, "JsonHandler", QString("List type %1 does not implement \"Q_INVOKABLE void put(QVariant variant)\" method!").arg(listTypeName).toUtf8());
}

void JsonHandler::registerObject(const QMetaObject &metaObject, const QMetaObject &listMetaObject)
{
    registerObject(metaObject);
    registerList(listMetaObject, metaObject);
}

QVariant JsonHandler::pack(const QMetaObject &metaObject, const void *value) const
{
    QString className = QString(metaObject.className()).split("::").last();
    if (m_listMetaObjects.contains(className)) {
        QVariantList ret;
        QMetaProperty countProperty = metaObject.property(metaObject.indexOfProperty("count"));
        QMetaObject entryMetaObject = m_metaObjects.value(m_listEntryTypes.value(className));
        int count = countProperty.readOnGadget(value).toInt();
        QMetaMethod getMethod = metaObject.method(metaObject.indexOfMethod("get(int)"));
        for (int i = 0; i < count; i++) {
            QVariant entry;
            getMethod.invokeOnGadget(const_cast<void*>(value), Q_RETURN_ARG(QVariant, entry), Q_ARG(int, i));
            ret.append(pack(entryMetaObject, entry.data()));
        }
        return ret;
    }

    if (m_metaObjects.contains(className)) {
        QVariantMap ret;
        for (int i = 0; i < metaObject.propertyCount(); i++) {
            QMetaProperty metaProperty = metaObject.property(i);

            // Skip QObject's objectName property
            if (metaProperty.name() == QStringLiteral("objectName")) {
                continue;
            }

            QVariant propertyValue = metaProperty.readOnGadget(value);
            // qCDebug(dcJsonRpc()) << metaProperty.name() << "optional:" << metaProperty.isUser()
            //                      << "value:" << propertyValue << "valid:" << propertyValue.isValid() << "null:" << propertyValue.isNull();

            // If it's optional and empty, we may skip it
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
            if (metaProperty.isUser()) {

                bool isEmpty = false;

                switch (propertyValue.typeId()) {
                case QMetaType::QString:
                    isEmpty = propertyValue.toString().isEmpty();
                    break;
                case QMetaType::QUuid:
                    isEmpty = propertyValue.toUuid().isNull();
                    break;
                default:
                    isEmpty = (!propertyValue.isValid() || propertyValue.isNull());
                    break;
                }

                if (isEmpty) {
                    // Optional and empty...skip this property
                    continue;
                }
            }
#else
            if (metaProperty.isUser() && (!propertyValue.isValid() || propertyValue.isNull())) {
                continue;
            }
#endif

#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
            // Pack flags
            if (metaProperty.isFlagType()) {
                QString enumName = QString(metaProperty.typeName()).split("::").last().remove('<').remove('>');
                QString flagName = m_flagsEnums.key(enumName);
                QMetaEnum metaFlag = m_metaFlags.value(flagName);
                Q_ASSERT_X(m_metaFlags.contains(flagName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(flagName).toUtf8());
                int flagValue = propertyValue.toInt();
                QStringList flags;
                for (int i = 0; i < metaFlag.keyCount(); i++) {
                    int flag = metaFlag.value(i) & flagValue;
                    if (flag == metaFlag.value(i) && flag > 0) {
                        flags.append(metaFlag.key(i));
                    }
                }
                ret.insert(metaProperty.name(), flags);
                continue;
            }

            // Pack enums
            if (metaProperty.isEnumType()) {
                QString enumName = QString(metaProperty.typeName()).split("::").last().remove('<').remove('>');
                Q_ASSERT_X(m_metaEnums.contains(enumName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(metaProperty.typeName()).toUtf8());
                QMetaEnum metaEnum = m_metaEnums.value(enumName);
                ret.insert(metaProperty.name(), metaEnum.key(propertyValue.toInt()));
                continue;
            }
#else
            // Pack flags
            if (metaProperty.isFlagType()) {
                QString flagName = QString(metaProperty.typeName()).split("::").last();
                QMetaEnum metaFlag = m_metaFlags.value(flagName);
                Q_ASSERT_X(m_metaFlags.contains(flagName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(flagName).toUtf8());
                int flagValue = propertyValue.toInt();
                QStringList flags;
                for (int i = 0; i < metaFlag.keyCount(); i++) {
                    int flag = metaFlag.value(i) & flagValue;
                    if (flag == metaFlag.value(i) && flag > 0) {
                        flags.append(metaFlag.key(i));
                    }
                }
                ret.insert(metaProperty.name(), flags);
                continue;
            }

            // Pack enums
            if (metaProperty.isEnumType()) {
                QString enumName = QString(metaProperty.typeName()).split("::").last();
                Q_ASSERT_X(m_metaEnums.contains(enumName), this->metaObject()->className(), QString("Cannot pack %1. %2 is not registered in this handler.").arg(className).arg(metaProperty.typeName()).toUtf8());
                QMetaEnum metaEnum = m_metaEnums.value(enumName);
                ret.insert(metaProperty.name(), metaEnum.key(propertyValue.toInt()));
                continue;
            }
#endif
            // Basic type/Variant type
            if (metaProperty.typeName() == QStringLiteral("QMetaType::Type")) {
                QMetaEnum metaEnum = QMetaEnum::fromType<BasicType>();
                ret.insert(metaProperty.name(), metaEnum.valueToKey(propertyValue.template value<QMetaType::Type>()));
                continue;
            }


            // Our own objects
            QString propertyTypeName = QString(metaProperty.typeName()).split("::").last();
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
            int typeId = metaProperty.typeId();
#else
            int typeId = metaProperty.type();
#endif
            if (typeId >= QMetaType::User) {
                if (m_listMetaObjects.contains(propertyTypeName)) {
                    QMetaObject entryMetaObject = m_listMetaObjects.value(propertyTypeName);
                    QVariant packed = pack(entryMetaObject, propertyValue.data());
                    if (!metaProperty.isUser() || packed.toList().count() > 0) {
                        ret.insert(metaProperty.name(), packed);
                    }
                    continue;
                }

                if (m_metaObjects.contains(propertyTypeName)) {
                    QMetaObject entryMetaObject = m_metaObjects.value(propertyTypeName);
                    QVariant packed = pack(entryMetaObject, propertyValue.data());
                    int isValidIndex = entryMetaObject.indexOfMethod("isValid()");
                    bool isValid = true;
                    if (isValidIndex >= 0) {
                        QMetaMethod isValidMethod = entryMetaObject.method(isValidIndex);
                        isValidMethod.invokeOnGadget(propertyValue.data(), Q_RETURN_ARG(bool, isValid));
                    }
                    if (isValid || !metaProperty.isUser()) {
                        ret.insert(metaProperty.name(), packed);
                    }
                    continue;
                }

                // Manually converting QList<BasicType>... Only QVariantList is known to the meta system
                if (propertyTypeName.startsWith("ParamList")) {
                    QVariantList list;
                    foreach (const Param &entry, propertyValue.value<ParamList>()) {
                        list << pack(entry);
                    }

                    ret.insert(metaProperty.name(), list);
                    continue;
                }

                // Manually converting QList<BasicType>... Only QVariantList is known to the meta system
                if (propertyTypeName.startsWith("QList<")) {
                    QVariantList list;
                    if (propertyTypeName == "QList<int>") {
                        foreach (int entry, propertyValue.value<QList<int>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<QUuid>") {
                        foreach (const QUuid &entry, propertyValue.value<QList<QUuid>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<ThingId>") {
                        foreach (const ThingId &entry, propertyValue.value<QList<ThingId>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<EventTypeId>") {
                        foreach (const EventTypeId &entry, propertyValue.value<QList<EventTypeId>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<StateTypeId>") {
                        foreach (const EventTypeId &entry, propertyValue.value<QList<StateTypeId>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<ActionTypeId>") {
                        foreach (const EventTypeId &entry, propertyValue.value<QList<ActionTypeId>>()) {
                            list << entry;
                        }
                    } else if (propertyTypeName == "QList<QDateTime>") {
                        foreach (const QDateTime &timestamp, propertyValue.value<QList<QDateTime>>()) {
                            list << timestamp.toSecsSinceEpoch();
                        }
                    } else {
                        Q_ASSERT_X(false, this->metaObject()->className(), QString("Unhandled list type: %1").arg(propertyTypeName).toUtf8());
                        qCWarning(dcJsonRpc()) << "Cannot pack property of unhandled list type" << propertyTypeName;
                    }

                    if (!list.isEmpty() || !metaProperty.isUser()) {
                        ret.insert(metaProperty.name(), list);
                    }
                    continue;
                }

                Q_ASSERT_X(false, this->metaObject()->className(), QString("Unregistered property type: %1").arg(propertyTypeName).toUtf8());
                qCWarning(dcJsonRpc()) << "Cannot pack property of unregistered object type" << propertyTypeName;
                continue;
            }

            // Standard properties, QString, int etc...
            // Special treatment for QDateTime (converting to time_t)
            if (typeId == QMetaType::QDateTime) {
                QDateTime dateTime = propertyValue.toDateTime();
                if (metaProperty.isUser() && (!dateTime.isValid() || dateTime.isNull())) {
                    continue;
                }
                propertyValue = propertyValue.toDateTime().toSecsSinceEpoch();
            } else if (typeId == QMetaType::QTime) {
                propertyValue = propertyValue.toTime().toString("hh:mm");
                if (metaProperty.isUser() && propertyValue.toString().isEmpty()) {
                    continue;
                }
            }

            ret.insert(metaProperty.name(), propertyValue);
        }
        return ret;
    }

    Q_ASSERT_X(false, this->metaObject()->className(), QString("Unregistered object type: %1").arg(className).toUtf8());
    qCWarning(dcJsonRpc()) << "Cannot pack object of unregistered type" << className;
    return QVariant();
}

QVariant JsonHandler::unpack(const QMetaObject &metaObject, const QVariant &value) const
{
    QString typeName = QString(metaObject.className()).split("::").last();

    // If it's a list object, loop over count
    if (m_listMetaObjects.contains(typeName)) {
        QVariantList list;
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
        QMetaType::Type valueType = static_cast<QMetaType::Type>(value.typeId());
#else
        QMetaType::Type valueType = static_cast<QMetaType::Type>(value.type());
#endif
        if (!value.isValid() || value.isNull()) {
            list = QVariantList();
        } else if (valueType == QMetaType::QVariantList || valueType == QMetaType::QStringList) {
            list = value.toList();
        } else {
            qCWarning(dcJsonRpc()) << "Cannot unpack" << typeName << ". Value is not in list format:" << value;
            return QVariant();
        }

        int typeId = QMetaType::type(metaObject.className());
        void* ptr = QMetaType::create(typeId);
        Q_ASSERT_X(typeId != 0, this->metaObject()->className(), QString("Cannot handle unregistered meta type %1").arg(metaObject.className()).toUtf8());

        QMetaObject entryMetaObject = m_metaObjects.value(m_listEntryTypes.value(typeName));
        QMetaMethod putMethod = metaObject.method(metaObject.indexOfMethod("put(QVariant)"));

        foreach (const QVariant &variant, list) {
            QVariant value = unpack(entryMetaObject, variant);
            putMethod.invokeOnGadget(ptr, Q_ARG(QVariant, value));
        }
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
        QVariant ret = QVariant(QMetaType(typeId), ptr);
#else
        QVariant ret = QVariant(static_cast<QVariant::Type>(typeId), ptr);
#endif
        QMetaType::destroy(typeId, ptr);
        return ret;
    }

    // If it's an object, loop over all properties
    if (m_metaObjects.contains(typeName)) {
        QVariantMap map = value.toMap();
        int typeId = QMetaType::type(metaObject.className());
        QMetaType metaType(typeId);
        Q_ASSERT_X(typeId != 0, this->metaObject()->className(), QString("Cannot handle unregistered meta type %1").arg(typeName).toUtf8());
        void* ptr = QMetaType::create(typeId);
        for (int i = 0; i < metaObject.propertyCount(); i++) {
            QMetaProperty metaProperty = metaObject.property(i);
            if (metaProperty.name() == QStringLiteral("objectName")) {
                continue;
            }
            if (!metaProperty.isWritable()) {
                continue;
            }
            if (!metaProperty.isUser()) {
                Q_ASSERT_X(map.contains(metaProperty.name()), this->metaObject()->className(), QString("Missing property %1 in map.").arg(metaProperty.name()).toUtf8());
            }

            if (map.contains(metaProperty.name())) {

                QString propertyTypeName = QString(metaProperty.typeName()).split("::").last();
                QVariant variant = map.value(metaProperty.name());

                // recurse into child lists
                if (m_listMetaObjects.contains(propertyTypeName)) {
                    QMetaObject propertyMetaObject = m_listMetaObjects.value(propertyTypeName);
                    metaProperty.writeOnGadget(ptr, unpack(propertyMetaObject, variant));
                    continue;
                }

                // recurse into child objects
                if (m_metaObjects.contains(propertyTypeName)) {
                    QMetaObject propertyMetaObject = m_metaObjects.value(propertyTypeName);
                    metaProperty.writeOnGadget(ptr, unpack(propertyMetaObject, variant));
                    continue;
                }

                if (QString(metaProperty.typeName()).startsWith("QList<")) {
                    if (metaProperty.typeName() == QStringLiteral("QList<int>")) {
                        QList<int> intList;
                        foreach (const QVariant &val, variant.toList()) {
                            intList.append(val.toInt());
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(intList));
                    } else if (metaProperty.typeName() == QStringLiteral("QList<QUuid>")) {
                        QList<QUuid> uuidList;
                        foreach (const QVariant &val, variant.toList()) {
                            uuidList.append(val.toUuid());
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(uuidList));
                    } else if (metaProperty.typeName() == QStringLiteral("QList<ThingId>")) {
                        QList<ThingId> thingIds;
                        foreach (const QVariant &val, variant.toList()) {
                            thingIds.append(ThingId(val.toUuid()));
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(thingIds));
                    } else if (metaProperty.typeName() == QStringLiteral("QList<EventTypeId>")) {
                        QList<EventTypeId> eventTypeIds;
                        foreach (const QVariant &val, variant.toList()) {
                            eventTypeIds.append(EventTypeId(val.toUuid()));
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(eventTypeIds));
                    } else if (metaProperty.typeName() == QStringLiteral("QList<StateTypeId>")) {
                        QList<StateTypeId> stateTypeIds;
                        foreach (const QVariant &val, variant.toList()) {
                            stateTypeIds.append(StateTypeId(val.toUuid()));
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(stateTypeIds));
                    } else if (metaProperty.typeName() == QStringLiteral("QList<ActionTypeId>")) {
                        QList<ActionTypeId> actionTypeIds;
                        foreach (const QVariant &val, variant.toList()) {
                            actionTypeIds.append(ActionTypeId(val.toUuid()));
                        }
                        metaProperty.writeOnGadget(ptr, QVariant::fromValue(actionTypeIds));
                    }
                    continue;
                }

#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
                int typeId = metaProperty.typeId();
#else
                int typeId = metaProperty.type();
#endif

                // Special treatment for QDateTime (convert from time_t)
                if (typeId == QMetaType::QDateTime) {
                    variant = QDateTime::fromSecsSinceEpoch(variant.toUInt());
                } else if (typeId == QMetaType::QTime) {
                    variant = QTime::fromString(variant.toString(), "hh:mm");
                }

                // For basic properties just write the veriant as is
                metaProperty.writeOnGadget(ptr, variant);
            }

        }
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
        QVariant ret = QVariant(QMetaType(typeId), ptr);
#else
        QVariant ret = QVariant(typeId, ptr);
#endif
        QMetaType::destroy(typeId, ptr);
        return ret;
    }

    return QVariant();
}
