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

#include "qquick3dxranchormanager_openxr_p.h"
#include "openxr/qopenxrhelpers_p.h"
#include "qquick3dxrspatialanchor_p.h"

#include <QtQuick3DUtils/private/qssgassert_p.h>

#include <QLoggingCategory>

#if defined(Q_OS_ANDROID)
# include <QtCore/private/qandroidextras_p.h>
#endif

QT_BEGIN_NAMESPACE

Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);

//<uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" />

static const uint32_t MAX_PERSISTENT_SPACES = 100;

static const char qssgXrRecognizedLabels[] = "TABLE,COUCH,FLOOR,CEILING,WALL_FACE,WINDOW_FRAME,DOOR_FRAME,STORAGE,BED,SCREEN,LAMP,PLANT,WALL_ART,OTHER";

[[nodiscard]] static QQuick3DXrSpatialAnchor::Classification getLabelForString(const QString &label)
{
    if (label == QStringLiteral("TABLE"))
        return QQuick3DXrSpatialAnchor::Classification::Table;
    if (label == QStringLiteral("COUCH"))
        return QQuick3DXrSpatialAnchor::Classification::Seat;
    if (label == QStringLiteral("FLOOR"))
        return QQuick3DXrSpatialAnchor::Classification::Floor;
    if (label == QStringLiteral("CEILING"))
        return QQuick3DXrSpatialAnchor::Classification::Ceiling;
    if (label == QStringLiteral("WALL_FACE"))
        return QQuick3DXrSpatialAnchor::Classification::Wall;
    if (label == QStringLiteral("WINDOW_FRAME"))
        return QQuick3DXrSpatialAnchor::Classification::Window;
    if (label == QStringLiteral("DOOR_FRAME"))
        return QQuick3DXrSpatialAnchor::Classification::Door;

    return QQuick3DXrSpatialAnchor::Classification::Other;
}


QQuick3DXrAnchorManager::QQuick3DXrAnchorManager()
{

}

QQuick3DXrAnchorManager::~QQuick3DXrAnchorManager()
{

}

QQuick3DXrAnchorManager *QQuick3DXrAnchorManager::instance()
{
    static QQuick3DXrAnchorManager instance;
    return &instance;
}

void QQuick3DXrAnchorManager::initialize(XrInstance instance, XrSession session)
{
#if defined(Q_OS_ANDROID)
    auto res = QtAndroidPrivate::requestPermission(QLatin1StringView("com.oculus.permission.USE_SCENE"));
    res.waitForFinished();
#endif

    m_instance = instance;
    m_session = session;

    // Get the function pointers
    resolveXrFunction(
                    "xrEnumerateSpaceSupportedComponentsFB",
                    (PFN_xrVoidFunction*)(&xrEnumerateSpaceSupportedComponentsFB));
    resolveXrFunction(
                    "xrGetSpaceComponentStatusFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceComponentStatusFB));
    resolveXrFunction(
                    "xrSetSpaceComponentStatusFB",
                    (PFN_xrVoidFunction*)(&xrSetSpaceComponentStatusFB));
    resolveXrFunction(
                    "xrGetSpaceUuidFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceUuidFB));
    resolveXrFunction(
                    "xrQuerySpacesFB",
                    (PFN_xrVoidFunction*)(&xrQuerySpacesFB));
    resolveXrFunction(
                    "xrRetrieveSpaceQueryResultsFB",
                    (PFN_xrVoidFunction*)(&xrRetrieveSpaceQueryResultsFB));
    resolveXrFunction(
                    "xrGetSpaceBoundingBox2DFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceBoundingBox2DFB));
    resolveXrFunction(
                    "xrGetSpaceBoundingBox3DFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceBoundingBox3DFB));
    resolveXrFunction(
                    "xrGetSpaceSemanticLabelsFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceSemanticLabelsFB));
    resolveXrFunction(
                    "xrGetSpaceBoundary2DFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceBoundary2DFB));
    resolveXrFunction(
                    "xrGetSpaceRoomLayoutFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceRoomLayoutFB));
    resolveXrFunction(
                    "xrGetSpaceContainerFB",
                    (PFN_xrVoidFunction*)(&xrGetSpaceContainerFB));
    resolveXrFunction(
                    "xrRequestSceneCaptureFB",
                    (PFN_xrVoidFunction*)(&xrRequestSceneCaptureFB));
}

void QQuick3DXrAnchorManager::teardown()
{

}

QList<const char *> QQuick3DXrAnchorManager::requiredExtensions() const
{
    return {
        // XR_EXT_UUID_EXTENSION_NAME, // ### Crashes on Quest 3. Theoretically required for XR_FB_spatial_entity (would work anyway, but is a validation error)
        XR_FB_SPATIAL_ENTITY_EXTENSION_NAME,
        XR_FB_SPATIAL_ENTITY_QUERY_EXTENSION_NAME,
        XR_FB_SPATIAL_ENTITY_STORAGE_EXTENSION_NAME,
        XR_FB_SPATIAL_ENTITY_CONTAINER_EXTENSION_NAME,
        XR_FB_SCENE_EXTENSION_NAME,
#ifdef Q_OS_ANDROID // no scene capture with Simulator as of version 57
        XR_FB_SCENE_CAPTURE_EXTENSION_NAME
#endif
    };
}

void QQuick3DXrAnchorManager::handleEvent(const XrEventDataBaseHeader *event)
{
    if (event->type == XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB) {
        qCDebug(lcQuick3DXr, "QQuick3DXrAnchorManager::handleEvent: received XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB");
        const XrEventDataSpaceSetStatusCompleteFB* setStatusComplete = (const XrEventDataSpaceSetStatusCompleteFB*)(event);
        if (setStatusComplete->result == XR_SUCCESS) {
            if (setStatusComplete->componentType == XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB) {
                addAnchor(setStatusComplete->space, setStatusComplete->uuid);
            }
        }
    } else if (event->type == XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB) {
        qCDebug(lcQuick3DXr, "QQuick3DXrAnchorManager::handleEvent: received XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB");
        const XrEventDataSceneCaptureCompleteFB* captureResult = (const XrEventDataSceneCaptureCompleteFB*)(event);
        if (captureResult->result == XR_SUCCESS) {
            Q_EMIT sceneCaptureCompleted();
            qCDebug(lcQuick3DXr,
                "QQuick3DXrAnchorManager::handleEvent: Scene capture (ID = %llu) succeeded",
                static_cast<long long unsigned int>(captureResult->requestId));
        } else {
            qCDebug(lcQuick3DXr,
                "QQuick3DXrAnchorManager::handleEvent: Scene capture (ID = %llu) failed with an error %d",
                static_cast<long long unsigned int>(captureResult->requestId),
                captureResult->result);
        }

    } else if (event->type == XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB) {
        qCDebug(lcQuick3DXr, "QQuick3DXrAnchorManager::handleEvent: received XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB");
        const XrEventDataSpaceQueryResultsAvailableFB* resultsAvailable = (const XrEventDataSpaceQueryResultsAvailableFB*)(event);

        XrSpaceQueryResultsFB queryResults{};
        queryResults.type = XR_TYPE_SPACE_QUERY_RESULTS_FB;
        queryResults.resultCapacityInput = 0;
        queryResults.resultCountOutput = 0;
        queryResults.results = nullptr;

        if (!checkXrResult(retrieveSpaceQueryResults(resultsAvailable->requestId, &queryResults))) {
            qWarning("Failed to retrieve space query results");
            return;
        }

        QVector<XrSpaceQueryResultFB> results(queryResults.resultCountOutput);
        queryResults.resultCapacityInput = results.size();
        queryResults.resultCountOutput = 0;
        queryResults.results = results.data();

        if (!checkXrResult(retrieveSpaceQueryResults(resultsAvailable->requestId, &queryResults))) {
            qWarning("Failed to retrieve space query results");
            return;
        }

        qCDebug(lcQuick3DXr, "retrieveSpaceQueryResults: num of results received: %d", queryResults.resultCountOutput);
        for (const auto &result : results) {
            if (isComponentSupported(result.space, XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB)) {
                XrSpaceComponentStatusSetInfoFB request = {
                                                            XR_TYPE_SPACE_COMPONENT_STATUS_SET_INFO_FB,
                                                            nullptr,
                                                            XR_SPACE_COMPONENT_TYPE_LOCATABLE_FB,
                                                            true,
                                                            0
                };
                XrAsyncRequestIdFB requestId;
                XrResult res = setSpaceComponentStatus(result.space, &request, &requestId);
                if (res == XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB) {
                    addAnchor(result.space, result.uuid);
                }
            }
        }
    } else if (event->type == XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB) {
        qCDebug(lcQuick3DXr, "QQuick3DXrAnchorManager::handleEvent: received XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB");
    }
}

void QQuick3DXrAnchorManager::requestSceneCapture()
{
    XrAsyncRequestIdFB requestId;
    XrSceneCaptureRequestInfoFB request{};
    request.type = XR_TYPE_SCENE_CAPTURE_REQUEST_INFO_FB;
    request.requestByteCount = 0;
    request.request = nullptr;
    if (!checkXrResult(requestSceneCapture(&request, &requestId)))
        qWarning("Failed to request scene capture");
}

bool QQuick3DXrAnchorManager::isComponentSupported(XrSpace space, XrSpaceComponentTypeFB type)
{
    uint32_t numComponents = 0;
    if (!checkXrResult(enumerateSpaceSupportedComponents(space, 0, &numComponents, nullptr))) {
        qWarning("Failed to enumerate supported space components");
        return false;
    }

    QVector<XrSpaceComponentTypeFB> components(numComponents);
    if (!checkXrResult(enumerateSpaceSupportedComponents(space, numComponents, &numComponents, components.data()))) {
        qWarning("Failed to enumerate supported space components");
        return false;
    }

    bool supported = false;
    for (const auto &component : components) {
        if (component == type) {
            supported = true;
            break;
        }
    }

    return supported;
}

bool QQuick3DXrAnchorManager::isComponentEnabled(XrSpace space, XrSpaceComponentTypeFB type)
{
    XrSpaceComponentStatusFB status = {XR_TYPE_SPACE_COMPONENT_STATUS_FB, nullptr, 0, 0};
    if (!checkXrResult(getSpaceComponentStatus(space, type, &status))) {
        qWarning("Failed to get space component status");
        return false;
    }
    return (status.enabled && !status.changePending);
}

bool QQuick3DXrAnchorManager::getBoundingBox2D(XrSpace space, QVector2D &offset, QVector2D &extent)
{
    if (isComponentEnabled(space, XR_SPACE_COMPONENT_TYPE_BOUNDED_2D_FB)) {
        // Get the 2D bounds
        XrRect2Df boundingBox2D;
        if (!checkXrResult(getSpaceBoundingBox2D(space, &boundingBox2D))) {
            qWarning("Failed to get bounding box 2D for space");
            return false;
        }
        offset = QVector2D(boundingBox2D.offset.x, boundingBox2D.offset.y);
        extent = QVector2D(boundingBox2D.extent.width, boundingBox2D.extent.height);
        return true;
    }
    return false;
}

bool QQuick3DXrAnchorManager::getBoundingBox3D(XrSpace space, QVector3D &offset, QVector3D &extent)
{
    if (isComponentEnabled(space, XR_SPACE_COMPONENT_TYPE_BOUNDED_3D_FB)) {
        // Get the 3D bounds
        XrRect3DfFB boundingBox3D;
        if (!checkXrResult(getSpaceBoundingBox3D(space, &boundingBox3D))) {
            qWarning("Failed to get bounding box 3D for space");
            return false;
        }
        offset = QVector3D(boundingBox3D.offset.x, boundingBox3D.offset.y, boundingBox3D.offset.z);
        extent = QVector3D(boundingBox3D.extent.width, boundingBox3D.extent.height, boundingBox3D.extent.depth);
        return true;
    }

    return false;
}

bool QQuick3DXrAnchorManager::setupSpatialAnchor(XrSpace space, QQuick3DXrSpatialAnchor &anchor)
{
    QVector2D extent2D;
    QVector2D offset2D;

    QVector3D extent3D;
    QVector3D offset3D;

    QSSG_ASSERT(space != XR_NULL_HANDLE, return false);

    const bool m_has2DBounds = getBoundingBox2D(space, offset2D, extent2D);
    const bool m_has3DBounds = getBoundingBox3D(space, offset3D, extent3D);
    if (isComponentSupported(space, XR_SPACE_COMPONENT_TYPE_SPACE_CONTAINER_FB) &&
        isComponentEnabled(space, XR_SPACE_COMPONENT_TYPE_SPACE_CONTAINER_FB)) {
        // Get the space container UUIDs
        anchor.setSpaceContainerUuids(collectSpaceContainerUuids(space));
    } else if (isComponentSupported(space, XR_SPACE_COMPONENT_TYPE_ROOM_LAYOUT_FB) &&
               isComponentEnabled(space, XR_SPACE_COMPONENT_TYPE_ROOM_LAYOUT_FB)) {
        anchor.setRoomLayoutUuids(collectRoomLayoutUuids(space));
    }

    if (m_has2DBounds)
        anchor.setBounds2D(offset2D, extent2D);
    if (m_has3DBounds)
        anchor.setBounds3D(offset3D, extent3D);

    auto stringLabel = getSemanticLabels(space);
    auto semanticLable = getLabelForString(stringLabel);

    anchor.setClassification(semanticLable);
    anchor.setClassificationString(stringLabel);

    return true;
}

namespace {
bool isValidUuid(const XrUuidEXT& uuid) {
    // The best, and reasonable way we can say if a uuid is valid, is to check if it's not null.
    // Anyting more then that is outside the scope of this function (There's no real way to check if a uuid is valid
    // that makese sense here anyways).
    return !QtQuick3DXr::isNullUuid(uuid.data);
}

QUuid fromXrUuidExt(XrUuidEXT uuid) {
    return QUuid::fromBytes(uuid.data);
}

XrUuidEXT fromQUuid(QUuid uuid) {
    XrUuidEXT xrUuid;
    auto bytes = uuid.toBytes();
    memcpy(xrUuid.data, bytes.data, XR_UUID_SIZE_EXT);
    return xrUuid;
}

}

QSet<QUuid> QQuick3DXrAnchorManager::collectRoomLayoutUuids(XrSpace space)
{
    XrRoomLayoutFB roomLayout{};
    roomLayout.type = XR_TYPE_ROOM_LAYOUT_FB;
    QVector<XrUuidEXT> wallUuids;
    QSet<QUuid> uuidSet;

    // First call
    if (!checkXrResult(getSpaceRoomLayout(space, &roomLayout))) {
        qWarning("Failed to get room layout");
        return uuidSet;
    }

    // If wallUuidCountOutput == 0, no walls in the component. The UUIDs of the ceiling and floor
    // has been returned if available
    if (roomLayout.wallUuidCountOutput != 0) {
        // Second call
        wallUuids.resize(roomLayout.wallUuidCountOutput);
        roomLayout.wallUuidCapacityInput = wallUuids.size();
        roomLayout.wallUuids = wallUuids.data();
        if (!checkXrResult(getSpaceRoomLayout(space, &roomLayout))) {
            qWarning("Failed to get room layout");
            return uuidSet;
        }
    }
    if (isValidUuid(roomLayout.floorUuid))
        uuidSet.insert(fromXrUuidExt(roomLayout.floorUuid));
    if (isValidUuid(roomLayout.ceilingUuid))
        uuidSet.insert(fromXrUuidExt(roomLayout.ceilingUuid));
    for (uint32_t i = 0; i < roomLayout.wallUuidCountOutput; i++)
        uuidSet.insert(fromXrUuidExt(roomLayout.wallUuids[i]));
    return uuidSet;
}

QSet<QUuid> QQuick3DXrAnchorManager::collectSpaceContainerUuids(XrSpace space)
{
    XrSpaceContainerFB spaceContainer{};
    spaceContainer.type = XR_TYPE_SPACE_CONTAINER_FB;
    QSet<QUuid> uuidSet;
    // First call
    if (!checkXrResult(getSpaceContainer(space, &spaceContainer))) {
        qWarning("Failed to get container");
        return uuidSet;
    }
    if (spaceContainer.uuidCountOutput != 0) {
        // Second call
        QVector<XrUuidEXT> uuids(spaceContainer.uuidCountOutput);
        spaceContainer.uuidCapacityInput = uuids.size();
        spaceContainer.uuids = uuids.data();
        if (!checkXrResult(getSpaceContainer(space, &spaceContainer))) {
            qWarning("Failed to get container");
            return uuidSet;
        }

        for (uint32_t i = 0; i < spaceContainer.uuidCountOutput; i++)
            uuidSet.insert(fromXrUuidExt(spaceContainer.uuids[i]));
    }

    return uuidSet;
}

QString QQuick3DXrAnchorManager::getSemanticLabels(const XrSpace space) {
    const XrSemanticLabelsSupportInfoFB semanticLabelsSupportInfo = {
                                                                     XR_TYPE_SEMANTIC_LABELS_SUPPORT_INFO_FB,
                                                                     nullptr,
                                                                     XR_SEMANTIC_LABELS_SUPPORT_MULTIPLE_SEMANTIC_LABELS_BIT_FB | XR_SEMANTIC_LABELS_SUPPORT_ACCEPT_DESK_TO_TABLE_MIGRATION_BIT_FB,
                                                                     qssgXrRecognizedLabels
    };

    XrSemanticLabelsFB labels{};
    labels.type = XR_TYPE_SEMANTIC_LABELS_FB;
    labels.next = &semanticLabelsSupportInfo;

    if (!isComponentEnabled(space, XR_SPACE_COMPONENT_TYPE_SEMANTIC_LABELS_FB))
        return QString();

    // First call.
    if (!checkXrResult(getSpaceSemanticLabels(space, &labels))) {
        qWarning("Failed to get semantic labels");
        return {};
    }
    // Second call
    QByteArray labelData(labels.bufferCountOutput, Qt::Uninitialized);
    labels.bufferCapacityInput = labelData.size();
    labels.buffer = labelData.data();
    if (!checkXrResult(getSpaceSemanticLabels(space, &labels))) {
        qWarning("Failed to get semantic labels");
        return {};
    }

    return QString::fromLocal8Bit(labelData);
}

bool QQuick3DXrAnchorManager::queryAllAnchors() {
    XrSpaceQueryInfoFB queryInfo = {
                                    XR_TYPE_SPACE_QUERY_INFO_FB,
                                    nullptr,
                                    XR_SPACE_QUERY_ACTION_LOAD_FB,
                                    MAX_PERSISTENT_SPACES,
                                    0,
                                    nullptr,
                                    nullptr};

    XrAsyncRequestIdFB requestId;
    return checkXrResult(querySpaces((XrSpaceQueryInfoBaseHeaderFB*)&queryInfo, &requestId));
}

const QList<QQuick3DXrSpatialAnchor *> &QQuick3DXrAnchorManager::anchors() const
{
    return m_anchors;
}

qsizetype QQuick3DXrAnchorManager::anchorCount() const
{
    return m_anchors.count();
}

void QQuick3DXrAnchorManager::updateAnchors(XrTime predictedDisplayTime, XrSpace appSpace)
{
    // basically for each anchor, we need to call xrLocateSpace
    for (auto &anchor : m_anchors) {
        XrSpaceLocation spaceLocation{};
        spaceLocation.type = XR_TYPE_SPACE_LOCATION;
        XrResult res = xrLocateSpace(QtQuick3DXr::fromXrSpaceId<XrSpace>(anchor->space()), appSpace, predictedDisplayTime, &spaceLocation);
        if (XR_UNQUALIFIED_SUCCESS(res)) {
            if ((spaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 &&
                (spaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) {

                // Update transform
                anchor->setPosition(QVector3D(spaceLocation.pose.position.x,
                                              spaceLocation.pose.position.y,
                                              spaceLocation.pose.position.z) * 100.0f);
                anchor->setRotation(QQuaternion(spaceLocation.pose.orientation.w,
                                                spaceLocation.pose.orientation.x,
                                                spaceLocation.pose.orientation.y,
                                                spaceLocation.pose.orientation.z));
            }
        }
    }
}

bool QQuick3DXrAnchorManager::queryAllAnchorsWithSpecificComponentEnabled(const XrSpaceComponentTypeFB componentType) {
    XrSpaceStorageLocationFilterInfoFB storageLocationFilterInfo = {
                                                                        XR_TYPE_SPACE_STORAGE_LOCATION_FILTER_INFO_FB,
                                                                        nullptr,
                                                                        XR_SPACE_STORAGE_LOCATION_LOCAL_FB
                                                                    };

    XrSpaceComponentFilterInfoFB componentFilterInfo = {
                                                            XR_TYPE_SPACE_COMPONENT_FILTER_INFO_FB,
                                                            &storageLocationFilterInfo,
                                                            componentType
                                                        };

    XrSpaceQueryInfoFB queryInfo = {
                                        XR_TYPE_SPACE_QUERY_INFO_FB,
                                        nullptr,
                                        XR_SPACE_QUERY_ACTION_LOAD_FB,
                                        MAX_PERSISTENT_SPACES,
                                        0,
                                        (XrSpaceFilterInfoBaseHeaderFB*)&componentFilterInfo,
                                        nullptr
                                    };

    XrAsyncRequestIdFB requestId;
    if (!checkXrResult(querySpaces((XrSpaceQueryInfoBaseHeaderFB*)&queryInfo, &requestId))) {
        qWarning("Failed to query spaces");
        return false;
    }
    return true;
}

bool QQuick3DXrAnchorManager::queryAnchorsByUuids(const QSet<QUuid>& uuidSet) {
    if (uuidSet.isEmpty())
        return false;

    QVector<XrUuidEXT> uuidsToQuery;

    for (const auto &uuid : uuidSet) {
        XrUuidEXT xrUuid = fromQUuid(uuid);
        uuidsToQuery.append(xrUuid);
    }

    XrSpaceStorageLocationFilterInfoFB storageLocationFilterInfo = {
                                                                        XR_TYPE_SPACE_STORAGE_LOCATION_FILTER_INFO_FB,
                                                                        nullptr,
                                                                        XR_SPACE_STORAGE_LOCATION_LOCAL_FB
                                                                    };

    XrSpaceUuidFilterInfoFB uuidFilterInfo = {
                                                XR_TYPE_SPACE_UUID_FILTER_INFO_FB,
                                                &storageLocationFilterInfo,
                                                (uint32_t)uuidsToQuery.size(),
                                                uuidsToQuery.data()
                                            };

    XrSpaceQueryInfoFB queryInfo = {
                                        XR_TYPE_SPACE_QUERY_INFO_FB,
                                        nullptr,
                                        XR_SPACE_QUERY_ACTION_LOAD_FB,
                                        MAX_PERSISTENT_SPACES,
                                        0,
                                        (XrSpaceFilterInfoBaseHeaderFB*)&uuidFilterInfo,
                                        nullptr
                                    };

    XrAsyncRequestIdFB requestId;
    if (!checkXrResult(querySpaces((XrSpaceQueryInfoBaseHeaderFB*)&queryInfo, &requestId))) {
        qWarning("Failed to query spaces");
        return false;
    }
    return true;
}

void QQuick3DXrAnchorManager::addAnchor(XrSpace space, XrUuidEXT uuid)
{
    auto quuid = fromXrUuidExt(uuid);
    // Check if we already have this anchor
    if (m_anchorsByUuid.contains(quuid))
        return;

    QQuick3DXrSpatialAnchor *anchor = new QQuick3DXrSpatialAnchor(QtQuick3DXr::toXrSpaceId(space), quuid, this);
    setupSpatialAnchor(space, *anchor);
    m_anchorsByUuid.insert(quuid, anchor);
    m_anchors.append(anchor);
    Q_EMIT anchorAdded(anchor);
}


XrResult QQuick3DXrAnchorManager::enumerateSpaceSupportedComponents(XrSpace space, uint32_t componentTypeCapacityInput, uint32_t *componentTypeCountOutput, XrSpaceComponentTypeFB *componentTypes)
{
    return xrEnumerateSpaceSupportedComponentsFB(space, componentTypeCapacityInput, componentTypeCountOutput, componentTypes);
}

XrResult QQuick3DXrAnchorManager::getSpaceComponentStatus(XrSpace space, XrSpaceComponentTypeFB componentType, XrSpaceComponentStatusFB *status)
{
    return xrGetSpaceComponentStatusFB(space, componentType, status);
}

XrResult QQuick3DXrAnchorManager::setSpaceComponentStatus(XrSpace space, const XrSpaceComponentStatusSetInfoFB *info, XrAsyncRequestIdFB *requestId)
{
    return xrSetSpaceComponentStatusFB(space, info, requestId);
}

XrResult QQuick3DXrAnchorManager::getSpaceUuid(XrSpace space, XrUuidEXT *uuid)
{
    return xrGetSpaceUuidFB(space, uuid);
}

XrResult QQuick3DXrAnchorManager::querySpaces(const XrSpaceQueryInfoBaseHeaderFB *info, XrAsyncRequestIdFB *requestId)
{
    return xrQuerySpacesFB(m_session, info, requestId);
}

XrResult QQuick3DXrAnchorManager::retrieveSpaceQueryResults(XrAsyncRequestIdFB requestId, XrSpaceQueryResultsFB *results)
{
    return xrRetrieveSpaceQueryResultsFB(m_session, requestId, results);
}

XrResult QQuick3DXrAnchorManager::getSpaceBoundingBox2D(XrSpace space, XrRect2Df *boundingBox2DOutput)
{
    return xrGetSpaceBoundingBox2DFB(m_session, space, boundingBox2DOutput);
}

XrResult QQuick3DXrAnchorManager::getSpaceBoundingBox3D(XrSpace space, XrRect3DfFB *boundingBox3DOutput)
{
    return xrGetSpaceBoundingBox3DFB(m_session, space, boundingBox3DOutput);
}

XrResult QQuick3DXrAnchorManager::getSpaceSemanticLabels(XrSpace space, XrSemanticLabelsFB *semanticLabelsOutput)
{
    return xrGetSpaceSemanticLabelsFB(m_session, space, semanticLabelsOutput);
}

XrResult QQuick3DXrAnchorManager::getSpaceBoundary2D(XrSpace space, XrBoundary2DFB *boundary2DOutput)
{
    return xrGetSpaceBoundary2DFB(m_session, space, boundary2DOutput);
}

XrResult QQuick3DXrAnchorManager::getSpaceRoomLayout(XrSpace space, XrRoomLayoutFB *roomLayoutOutput)
{
    return xrGetSpaceRoomLayoutFB(m_session, space, roomLayoutOutput);
}

XrResult QQuick3DXrAnchorManager::getSpaceContainer(XrSpace space, XrSpaceContainerFB *spaceContainerOutput)
{
    return xrGetSpaceContainerFB(m_session, space, spaceContainerOutput);
}

XrResult QQuick3DXrAnchorManager::requestSceneCapture(const XrSceneCaptureRequestInfoFB *info, XrAsyncRequestIdFB *requestId)
{
    if (xrRequestSceneCaptureFB)
        return xrRequestSceneCaptureFB(m_session, info, requestId);
    else
        return XR_ERROR_FUNCTION_UNSUPPORTED;
}

bool QQuick3DXrAnchorManager::checkXrResult(const XrResult &result)
{
    return OpenXRHelpers::checkXrResult(result, m_instance);
}

bool QQuick3DXrAnchorManager::resolveXrFunction(const char *name, PFN_xrVoidFunction *function)
{
    XrResult result = xrGetInstanceProcAddr(m_instance, name, function);
    if (!OpenXRHelpers::checkXrResult(result, m_instance)) {
        qWarning("Failed to resolve OpenXR function %s", name);
        *function = nullptr;
        return false;
    }
    return true;
}

QT_END_NAMESPACE
