// Copyright (C) 2025 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 <QtQmlModels/private/qqmlfunctionsorter_p.h>
#include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h>
#include <QtQml/private/qqmlobjectcreator_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype FunctionSorter
    \inherits Sorter
    \inqmlmodule QtQml.Models
    \since 6.10
    \preliminary
    \brief Sorts data in a \l SortFilterProxyModel based on the evaluation of
    the designated 'sort' method.

    FunctionSorter allows user to define the designated 'sort' method and it
    will be evaluated to sort the data. The method takes two arguments
    (lhs and rhs) of the specified parameter type and the data can
    be accessed as below for evaluation,

    \qml
    SortFilterProxyModel {
        model: sourceModel
        sorters: [
            FunctionSorter {
                id: functionSorter
                component RoleData: QtObject {
                    property real age
                }
                function sort(lhsData: RoleData, rhsData: RoleData) : int {
                    return (lhsData.age < rhsData.age) ? -1 : ((lhsData === rhsData.age) ? 0 : 1)
                }
            }
        ]
    }
    \endqml

    \note The user needs to explicitly invoke
    \l{SortFilterProxyModel::invalidateSorter} whenever any external qml
    property used within the designated 'sort' method changes. This behaviour
    is subject to change in the future, like implicit invalidation and thus the
    user doesn't need to explicitly invoke
    \l{SortFilterProxyModel::invalidateSorter}.
*/

QQmlFunctionSorter::QQmlFunctionSorter(QObject *parent)
    : QQmlSorterBase (new QQmlFunctionSorterPrivate, parent)
{
}

QQmlFunctionSorter::~QQmlFunctionSorter()
{
    Q_D(QQmlFunctionSorter);
    if (d->m_lhsParameterData.metaType().flags() & QMetaType::PointerToQObject)
        delete d->m_lhsParameterData.value<QObject *>();
    if (d->m_rhsParameterData.metaType().flags() & QMetaType::PointerToQObject)
        delete d->m_rhsParameterData.value<QObject *>();
}

void QQmlFunctionSorter::componentComplete()
{
    Q_D(QQmlFunctionSorter);
    const auto *metaObj = this->metaObject();
    for (int idx = metaObj->methodOffset(); idx < metaObj->methodCount(); idx++) {
        // Once we find the method signature, break the loop
        QMetaMethod method = metaObj->method(idx);
        if (method.nameView() == "sort") {
            d->m_method = method;
            break;
        }
    }

    if (!d->m_method.isValid())
        return;

    if (d->m_method.parameterCount() != 2) {
        qWarning("sort method requires two parameters");
        return;
    }

    QQmlData *data = QQmlData::get(this);
    if (!data || !data->outerContext) {
        qWarning("sort requires a QML context");
        return;
    }

    const QMetaType parameterType = d->m_method.parameterMetaType(0);
    if (parameterType != d->m_method.parameterMetaType(1)) {
        qWarning("sort parameters have to be equal");
        return;
    }

    auto cu = QQmlMetaType::obtainCompilationUnit(parameterType);
    const QQmlType parameterQmlType = QQmlMetaType::qmlType(parameterType);

    QQmlRefPointer<QQmlContextData> context = data->outerContext;
    QQmlEngine *engine = context->engine();

    // The code below creates an instance of the inline component, composite,
    // or specific C++ QObject types. The created instance, along with the
    // data, are passed as an arguments to the 'sort' method, which is invoked
    // during the call to QQmlFunctionSorter::compare.
    // To create an instance of required component types (be it inline or
    // composite), an executable compilation unit is required, and this can be
    // obtained by looking up via metatype in the type registry
    // (QQmlMetaType::obtainCompilationUnit). Pass it through the QML engine to
    // make it executable. Further, use the executable compilation unit to run
    // an object creator and produce an instance.
    if (parameterType.flags() & QMetaType::PointerToQObject) {
        QObject *created0 = nullptr;
        QObject *created1 = nullptr;
        if (parameterQmlType.isInlineComponentType()) {
            const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
            const QString icName = parameterQmlType.elementName();
            created0 = QQmlObjectCreator(context, executableCu, context, icName).create(
                    executableCu->inlineComponentId(icName), nullptr, nullptr,
                    QQmlObjectCreator::InlineComponent);
            created1 = QQmlObjectCreator(context, executableCu, context, icName).create(
                    executableCu->inlineComponentId(icName), nullptr, nullptr,
                    QQmlObjectCreator::InlineComponent);
        } else if (parameterQmlType.isComposite()) {
            const auto executableCu = engine->handle()->executableCompilationUnit(std::move(cu));
            created0 = QQmlObjectCreator(context, executableCu, context, QString()).create();
            created1 = QQmlObjectCreator(context, executableCu, context, QString()).create();
        } else {
            created0 = parameterQmlType.metaObject()->newInstance();
            created1 = parameterQmlType.metaObject()->newInstance();
        }

        const auto names = d->m_method.parameterNames();
        created0->setObjectName(names[0]);
        created1->setObjectName(names[1]);
        d->m_lhsParameterData = QVariant::fromValue(created0);
        d->m_rhsParameterData = QVariant::fromValue(created1);
    } else {
        d->m_lhsParameterData = QVariant(parameterType);
        d->m_rhsParameterData = QVariant(parameterType);
    }
}

/*!
    \internal
*/
QPartialOrdering QQmlFunctionSorter::compare(
        const QModelIndex& sourceLeft, const QModelIndex& sourceRight,
        const QQmlSortFilterProxyModel *proxyModel) const
{
    Q_D(const QQmlFunctionSorter);
    if (!d->m_method.isValid()
            || !d->m_lhsParameterData.isValid()
            || !d->m_rhsParameterData.isValid()) {
        return QPartialOrdering::Unordered;
    }

    int retVal = 0;
    QSortFilterProxyModelHelper::setProperties(&d->m_lhsParameterData, proxyModel, sourceLeft);
    QSortFilterProxyModelHelper::setProperties(&d->m_rhsParameterData, proxyModel, sourceRight);

    void *argv[] = {&retVal, d->m_lhsParameterData.data(), d->m_rhsParameterData.data()};
    QMetaObject::metacall(
            const_cast<QQmlFunctionSorter *>(this), QMetaObject::InvokeMetaMethod,
            d->m_method.methodIndex(), argv);

    return (retVal == 0)
            ? QPartialOrdering::Equivalent
            : ((retVal < 0) ? QPartialOrdering::Less : QPartialOrdering::Greater);
}

QT_END_NAMESPACE

#include "moc_qqmlfunctionsorter_p.cpp"
