/**
 * @file device_group.c
 * @brief Multi-Vendor Vulkan Device Group Emulator
 *
 * Implements VK_KHR_device_group emulation for heterogeneous GPUs from
 * different vendors (NVIDIA, AMD, Intel, Moore Threads). This allows
 * applications to see multiple physical GPUs as a single logical device
 * with unified UUIDs and device properties.
 *
 * Key Features:
 * - Virtual UUID generation across heterogeneous vendors
 * - Device group property aggregation
 * - Peer memory feature emulation
 * - Cross-vendor synchronization primitives
 * - VK_KHR_device_group_creation support
 *
 * Copyright (C) 2026 MVGAL Project
 * SPDX-License-Identifier: MIT
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#include <assert.h>

#include <vulkan/vulkan.h>
#include "mvgal/mvgal.h"
#include "mvgal/mvgal_gpu.h"

/* Internal forward declarations (defined in physical_device.c) */
void mvgal_get_queue_family_properties(uint32_t* pCount,
                                        VkQueueFamilyProperties* pProperties);

/* ============================================================================
 * Device Group Constants and Types
 * ============================================================================ */

#define MAX_DEVICE_GROUP_SIZE 16
#define MVGAL_DEVICE_GROUP_MAGIC 0x4D564741 /* 'MVGA' */
#define MVGAL_UUID_SIZE VK_UUID_SIZE

/**
 * @brief Virtual UUID types
 */
typedef enum {
    MVGAL_UUID_TYPE_PIPELINE_CACHE = 0,
    MVGAL_UUID_TYPE_DEVICE_ID,
    MVGAL_UUID_TYPE_DRIVER_ID,
    MVGAL_UUID_TYPE_LUID,
    MVGAL_UUID_TYPE_COUNT
} mvgal_uuid_type_t;

/**
 * @brief Per-GPU device group member info
 */
typedef struct {
    uint32_t gpu_index;
    VkPhysicalDevice physical_device;
    VkPhysicalDeviceProperties properties;
    VkPhysicalDeviceMemoryProperties memory_properties;
    VkPhysicalDeviceFeatures features;
    VkDeviceSize local_heap_size;
    uint32_t queue_family_count;
    VkQueueFamilyProperties *queue_families;
    
    /* Peer access info */
    bool can_access_peers[MAX_DEVICE_GROUP_SIZE];
    VkPeerMemoryFeatureFlags peer_features[MAX_DEVICE_GROUP_SIZE];
    
    /* UUID info */
    uint8_t original_uuid[VK_UUID_SIZE];
    uint8_t virtual_uuid[VK_UUID_SIZE];
    
} mvgal_device_group_member_t;

/**
 * @brief Device group state
 */
typedef struct {
    uint32_t magic;
    uint32_t device_count;
    uint32_t render_device_mask;
    uint32_t present_device_mask;
    
    /* Group members */
    mvgal_device_group_member_t members[MAX_DEVICE_GROUP_SIZE];
    
    /* Aggregated properties */
    VkPhysicalDeviceProperties aggregated_properties;
    VkPhysicalDeviceMemoryProperties aggregated_memory;
    VkPhysicalDeviceFeatures aggregated_features;
    
    /* Virtual UUIDs */
    uint8_t virtual_pipeline_cache_uuid[VK_UUID_SIZE];
    uint8_t virtual_driver_uuid[VK_UUID_SIZE];
    uint8_t virtual_device_uuid[VK_UUID_SIZE];
    uint8_t virtual_luid[VK_LUID_SIZE_KHR];
    bool luid_valid;
    
    /* Group capabilities */
    VkDeviceGroupPresentCapabilitiesKHR present_caps;
    VkDeviceGroupDeviceCreateInfoKHR create_info;
    
    /* Synchronization */
    pthread_mutex_t lock;
    bool initialized;
    
} mvgal_device_group_t;

/* Global device group state */
static mvgal_device_group_t g_device_group = {0};
static pthread_mutex_t g_group_lock = PTHREAD_MUTEX_INITIALIZER;

/* ============================================================================
 * UUID Generation and Management
 * ============================================================================ */

/**
 * @brief Generate deterministic virtual UUID from GPU indices
 */
static void generate_virtual_uuid(const uint32_t *gpu_indices, 
                                   uint32_t count,
                                   uint8_t *uuid_out,
                                   mvgal_uuid_type_t type)
{
    /* Initialize with magic and type */
    memset(uuid_out, 0, VK_UUID_SIZE);
    
    /* MVGAL signature in first 4 bytes */
    uuid_out[0] = 0x4D; /* 'M' */
    uuid_out[1] = 0x56; /* 'V' */
    uuid_out[2] = 0x47; /* 'G' */
    uuid_out[3] = 0x41; /* 'A' */
    
    /* UUID type in byte 4 */
    uuid_out[4] = (uint8_t)type;
    
    /* Device count in byte 5 */
    uuid_out[5] = (uint8_t)count;
    
    /* Hash of GPU indices in bytes 6-15 */
    uint32_t hash = MVGAL_DEVICE_GROUP_MAGIC;
    for (uint32_t i = 0; i < count; i++) {
        hash = hash * 31 + gpu_indices[i];
        if (i < 8) {
            uuid_out[6 + i] = (uint8_t)(gpu_indices[i] & 0xFF);
        }
    }
    
    /* Mix in hash at end */
    memcpy(&uuid_out[14], &hash, sizeof(uint16_t));
}

/**
 * @brief Generate virtual LUID (Locally Unique Identifier)
 */
static void generate_virtual_luid(const uint32_t *gpu_indices,
                                 uint32_t count,
                                 uint8_t *luid_out,
                                 bool *luid_valid_out)
{
    /* LUID is 8 bytes on Windows, we emulate this for cross-platform compatibility */
    memset(luid_out, 0, VK_LUID_SIZE_KHR);
    
    /* MVGAL signature */
    luid_out[0] = 0x4D;
    luid_out[1] = 0x56;
    
    /* Device count */
    luid_out[2] = (uint8_t)count;
    
    /* Hash of indices */
    uint64_t hash = MVGAL_DEVICE_GROUP_MAGIC;
    for (uint32_t i = 0; i < count; i++) {
        hash = hash * 31 + gpu_indices[i];
    }
    
    memcpy(&luid_out[3], &hash, sizeof(uint64_t) - 3);
    
    *luid_valid_out = true;
}

/**
 * @brief Generate consistent device ID from vendor/device IDs
 */
static uint32_t generate_consistent_device_id(const mvgal_gpu_descriptor_t *gpus,
                                               uint32_t count)
{
    uint32_t id = 0;
    for (uint32_t i = 0; i < count; i++) {
        id ^= gpus[i].vendor_id ^ gpus[i].device_id;
        id = (id << 1) | (id >> 31); /* Rotate */
    }
    return id ? id : 0x1A4B0001; /* Fallback MVGAL ID */
}

/* ============================================================================
 * Property Aggregation
 * ============================================================================ */

/**
 * @brief Aggregate device properties from all group members
 */
static void aggregate_device_properties(mvgal_device_group_t *group)
{
    VkPhysicalDeviceProperties *agg = &group->aggregated_properties;
    
    /* Start with first device properties */
    agg->apiVersion = VK_API_VERSION_1_3;
    agg->driverVersion = VK_MAKE_VERSION(0, 2, 2);
    agg->vendorID = 0x1A4B; /* MVGAL vendor ID */
    agg->deviceID = generate_consistent_device_id(NULL, group->device_count);
    agg->deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU;
    
    snprintf((char*)agg->deviceName, VK_MAX_PHYSICAL_DEVICE_NAME_SIZE,
             "MVGAL Virtual Multi-GPU (%u devices)", group->device_count);
    
    /* pipelineCacheUUID is the only UUID field on VkPhysicalDeviceProperties (Vulkan 1.0 core). */
    memcpy(agg->pipelineCacheUUID, group->virtual_pipeline_cache_uuid, VK_UUID_SIZE);
    
    /* Aggregate limits - take minimums for intersection */
    for (uint32_t i = 0; i < group->device_count; i++) {
        VkPhysicalDeviceLimits *limits = &group->members[i].properties.limits;
        
        if (i == 0) {
            agg->limits = *limits;
        } else {
            /* Conservative: take minimums for compatibility */
            #define MIN_LIMIT(field) agg->limits.field = agg->limits.field < limits->field ? agg->limits.field : limits->field
            #define MAX_LIMIT(field) agg->limits.field = agg->limits.field > limits->field ? agg->limits.field : limits->field
            
            MIN_LIMIT(maxImageDimension1D);
            MIN_LIMIT(maxImageDimension2D);
            MIN_LIMIT(maxImageDimension3D);
            MIN_LIMIT(maxImageDimensionCube);
            MIN_LIMIT(maxImageArrayLayers);
            MIN_LIMIT(maxTexelBufferElements);
            MIN_LIMIT(maxUniformBufferRange);
            MIN_LIMIT(maxStorageBufferRange);
            MIN_LIMIT(maxPushConstantsSize);
            MIN_LIMIT(maxMemoryAllocationCount);
            MIN_LIMIT(maxSamplerAllocationCount);
            MIN_LIMIT(bufferImageGranularity);
            MIN_LIMIT(sparseAddressSpaceSize);
            MIN_LIMIT(maxBoundDescriptorSets);
            MIN_LIMIT(maxPerStageDescriptorSamplers);
            MIN_LIMIT(maxPerStageDescriptorUniformBuffers);
            MIN_LIMIT(maxPerStageDescriptorStorageBuffers);
            MIN_LIMIT(maxPerStageDescriptorSampledImages);
            MIN_LIMIT(maxPerStageDescriptorStorageImages);
            MIN_LIMIT(maxPerStageDescriptorInputAttachments);
            MIN_LIMIT(maxPerStageResources);
            MIN_LIMIT(maxDescriptorSetSamplers);
            MIN_LIMIT(maxDescriptorSetUniformBuffers);
            MIN_LIMIT(maxDescriptorSetUniformBuffersDynamic);
            MIN_LIMIT(maxDescriptorSetStorageBuffers);
            MIN_LIMIT(maxDescriptorSetStorageBuffersDynamic);
            MIN_LIMIT(maxDescriptorSetSampledImages);
            MIN_LIMIT(maxDescriptorSetStorageImages);
            MIN_LIMIT(maxDescriptorSetInputAttachments);
            MIN_LIMIT(maxVertexInputAttributes);
            MIN_LIMIT(maxVertexInputBindings);
            MIN_LIMIT(maxVertexInputAttributeOffset);
            MIN_LIMIT(maxVertexInputBindingStride);
            MIN_LIMIT(maxVertexOutputComponents);
            MIN_LIMIT(maxTessellationGenerationLevel);
            MIN_LIMIT(maxTessellationPatchSize);
            MIN_LIMIT(maxTessellationControlPerVertexInputComponents);
            MIN_LIMIT(maxTessellationControlPerVertexOutputComponents);
            MIN_LIMIT(maxTessellationControlPerPatchOutputComponents);
            MIN_LIMIT(maxTessellationControlTotalOutputComponents);
            MIN_LIMIT(maxTessellationEvaluationInputComponents);
            MIN_LIMIT(maxTessellationEvaluationOutputComponents);
            MIN_LIMIT(maxGeometryShaderInvocations);
            MIN_LIMIT(maxGeometryInputComponents);
            MIN_LIMIT(maxGeometryOutputComponents);
            MIN_LIMIT(maxGeometryOutputVertices);
            MIN_LIMIT(maxGeometryTotalOutputComponents);
            MIN_LIMIT(maxFragmentInputComponents);
            MIN_LIMIT(maxFragmentOutputAttachments);
            MIN_LIMIT(maxFragmentDualSrcAttachments);
            MIN_LIMIT(maxFragmentCombinedOutputResources);
            MIN_LIMIT(maxComputeSharedMemorySize);
            MIN_LIMIT(maxComputeWorkGroupCount[0]);
            MIN_LIMIT(maxComputeWorkGroupCount[1]);
            MIN_LIMIT(maxComputeWorkGroupCount[2]);
            MIN_LIMIT(maxComputeWorkGroupInvocations);
            MIN_LIMIT(maxComputeWorkGroupSize[0]);
            MIN_LIMIT(maxComputeWorkGroupSize[1]);
            MIN_LIMIT(maxComputeWorkGroupSize[2]);
            MIN_LIMIT(subPixelPrecisionBits);
            MIN_LIMIT(subTexelPrecisionBits);
            MIN_LIMIT(mipmapPrecisionBits);
            MIN_LIMIT(maxDrawIndexedIndexValue);
            MIN_LIMIT(maxDrawIndirectCount);
            MIN_LIMIT(maxSamplerLodBias);
            MIN_LIMIT(maxSamplerAnisotropy);
            MIN_LIMIT(maxViewports);
            MIN_LIMIT(maxViewportDimensions[0]);
            MIN_LIMIT(maxViewportDimensions[1]);
            MIN_LIMIT(viewportBoundsRange[0]);
            MIN_LIMIT(viewportBoundsRange[1]);
            MIN_LIMIT(viewportSubPixelBits);
            MIN_LIMIT(minMemoryMapAlignment);
            MIN_LIMIT(minTexelBufferOffsetAlignment);
            MIN_LIMIT(minUniformBufferOffsetAlignment);
            MIN_LIMIT(minStorageBufferOffsetAlignment);
            MIN_LIMIT(minTexelOffset);
            MAX_LIMIT(maxTexelOffset);
            MIN_LIMIT(minTexelGatherOffset);
            MAX_LIMIT(maxTexelGatherOffset);
            MIN_LIMIT(minInterpolationOffset);
            MAX_LIMIT(maxInterpolationOffset);
            MIN_LIMIT(subPixelInterpolationOffsetBits);
            MIN_LIMIT(maxFramebufferWidth);
            MIN_LIMIT(maxFramebufferHeight);
            MIN_LIMIT(maxFramebufferLayers);
            MIN_LIMIT(framebufferColorSampleCounts);
            MIN_LIMIT(framebufferDepthSampleCounts);
            MIN_LIMIT(framebufferStencilSampleCounts);
            MIN_LIMIT(framebufferNoAttachmentsSampleCounts);
            MIN_LIMIT(maxColorAttachments);
            MIN_LIMIT(sampledImageColorSampleCounts);
            MIN_LIMIT(sampledImageIntegerSampleCounts);
            MIN_LIMIT(sampledImageDepthSampleCounts);
            MIN_LIMIT(sampledImageStencilSampleCounts);
            MIN_LIMIT(storageImageSampleCounts);
            MIN_LIMIT(maxSampleMaskWords);
            MIN_LIMIT(timestampComputeAndGraphics);
            MIN_LIMIT(timestampPeriod);
            MIN_LIMIT(maxClipDistances);
            MIN_LIMIT(maxCullDistances);
            MIN_LIMIT(maxCombinedClipAndCullDistances);
            MIN_LIMIT(discreteQueuePriorities);
            MIN_LIMIT(pointSizeRange[0]);
            MAX_LIMIT(pointSizeRange[1]);
            MIN_LIMIT(lineWidthRange[0]);
            MAX_LIMIT(lineWidthRange[1]);
            MIN_LIMIT(pointSizeGranularity);
            MIN_LIMIT(lineWidthGranularity);
            MIN_LIMIT(strictLines);
            MIN_LIMIT(standardSampleLocations);
            MIN_LIMIT(optimalBufferCopyOffsetAlignment);
            MIN_LIMIT(optimalBufferCopyRowPitchAlignment);
            MIN_LIMIT(nonCoherentAtomSize);
            
            #undef MIN_LIMIT
            #undef MAX_LIMIT
        }
    }
}

/**
 * @brief Aggregate memory properties
 */
static void aggregate_memory_properties(mvgal_device_group_t *group)
{
    VkPhysicalDeviceMemoryProperties *agg = &group->aggregated_memory;
    
    /* Sum up memory heaps from all devices */
    agg->memoryHeapCount = 0;
    agg->memoryTypeCount = 0;
    
    uint32_t heap_index_map[MAX_DEVICE_GROUP_SIZE] = {0};
    
    for (uint32_t i = 0; i < group->device_count; i++) {
        VkPhysicalDeviceMemoryProperties *mem = &group->members[i].memory_properties;
        
        /* Map this device's heaps to global heap indices */
        for (uint32_t h = 0; h < mem->memoryHeapCount; h++) {
            if (agg->memoryHeapCount < VK_MAX_MEMORY_HEAPS) {
                heap_index_map[h] = agg->memoryHeapCount;
                agg->memoryHeaps[agg->memoryHeapCount] = mem->memoryHeaps[h];
                
                /* Tag the heap with device index for identification */
                /* Note: We can't actually tag in standard Vulkan, but we track this internally */
                
                agg->memoryHeapCount++;
            }
        }
        
        /* Add memory types with updated heap indices */
        for (uint32_t t = 0; t < mem->memoryTypeCount; t++) {
            if (agg->memoryTypeCount < VK_MAX_MEMORY_TYPES) {
                agg->memoryTypes[agg->memoryTypeCount] = mem->memoryTypes[t];
                agg->memoryTypes[agg->memoryTypeCount].heapIndex = 
                    heap_index_map[mem->memoryTypes[t].heapIndex];
                agg->memoryTypeCount++;
            }
        }
    }
}

/**
 * @brief Aggregate device features - intersection of all devices
 */
static void aggregate_device_features(mvgal_device_group_t *group)
{
    VkPhysicalDeviceFeatures *agg = &group->aggregated_features;
    
    if (group->device_count == 0) {
        memset(agg, 0, sizeof(*agg));
        return;
    }
    
    /* Start with first device's features */
    *agg = group->members[0].features;
    
    /* AND with each subsequent device's features */
    for (uint32_t i = 1; i < group->device_count; i++) {
        VkBool32 *agg_fields = (VkBool32*)agg;
        VkBool32 *dev_fields = (VkBool32*)&group->members[i].features;
        
        for (size_t f = 0; f < sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32); f++) {
            agg_fields[f] = agg_fields[f] && dev_fields[f];
        }
    }
}

/* ============================================================================
 * Public API Implementation
 * ============================================================================ */

mvgal_error_t mvgal_device_group_init(void)
{
    pthread_mutex_lock(&g_group_lock);
    
    if (g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_SUCCESS;
    }
    
    memset(&g_device_group, 0, sizeof(g_device_group));
    g_device_group.magic = MVGAL_DEVICE_GROUP_MAGIC;
    
    if (pthread_mutex_init(&g_device_group.lock, NULL) != 0) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_INITIALIZATION;
    }
    
    g_device_group.initialized = true;
    
    pthread_mutex_unlock(&g_group_lock);
    
    mvgal_log_info("Device group emulator initialized");
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_shutdown(void)
{
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_SUCCESS;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    /* Cleanup member queue families */
    for (uint32_t i = 0; i < g_device_group.device_count; i++) {
        free(g_device_group.members[i].queue_families);
    }
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_destroy(&g_device_group.lock);
    
    memset(&g_device_group, 0, sizeof(g_device_group));
    
    pthread_mutex_unlock(&g_group_lock);
    
    mvgal_log_info("Device group emulator shutdown");
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_create(const uint32_t *gpu_indices,
                                         uint32_t gpu_count,
                                         VkPhysicalDevice *physical_devices)
{
    if (!gpu_indices || gpu_count == 0 || gpu_count > MAX_DEVICE_GROUP_SIZE) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    /* Reset existing group: never memset a live pthread_mutex_t */
    for (uint32_t i = 0; i < g_device_group.device_count; i++) {
        free(g_device_group.members[i].queue_families);
    }
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_destroy(&g_device_group.lock);

    memset(&g_device_group, 0, sizeof(g_device_group));
    g_device_group.magic = MVGAL_DEVICE_GROUP_MAGIC;

    if (pthread_mutex_init(&g_device_group.lock, NULL) != 0) {
        g_device_group.initialized = false;
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_INITIALIZATION;
    }

    g_device_group.initialized = true;
    pthread_mutex_lock(&g_device_group.lock);
    
    /* Generate virtual UUIDs */
    generate_virtual_uuid(gpu_indices, gpu_count,
                          g_device_group.virtual_pipeline_cache_uuid,
                          MVGAL_UUID_TYPE_PIPELINE_CACHE);
    
    generate_virtual_uuid(gpu_indices, gpu_count,
                          g_device_group.virtual_driver_uuid,
                          MVGAL_UUID_TYPE_DRIVER_ID);
    
    generate_virtual_uuid(gpu_indices, gpu_count,
                          g_device_group.virtual_device_uuid,
                          MVGAL_UUID_TYPE_DEVICE_ID);
    
    generate_virtual_luid(gpu_indices, gpu_count,
                          g_device_group.virtual_luid,
                          &g_device_group.luid_valid);
    
    /* Populate group members */
    g_device_group.device_count = gpu_count;
    g_device_group.render_device_mask = (1U << gpu_count) - 1;
    g_device_group.present_device_mask = (1U << gpu_count) - 1;
    
    for (uint32_t i = 0; i < gpu_count; i++) {
        mvgal_device_group_member_t *member = &g_device_group.members[i];
        member->gpu_index = gpu_indices[i];
        
        if (physical_devices) {
            member->physical_device = physical_devices[i];
        }
        
        /* Get GPU descriptor */
        mvgal_gpu_descriptor_t gpu_desc;
        if (mvgal_gpu_get_descriptor(gpu_indices[i], &gpu_desc) == MVGAL_SUCCESS) {
            /* Store original UUID */
            memcpy(member->original_uuid, &gpu_desc.vendor_id, 
                   sizeof(uint16_t) < VK_UUID_SIZE ? sizeof(uint16_t) : VK_UUID_SIZE);
        }
        
        /* Generate virtual UUID for this member */
        generate_virtual_uuid(&gpu_indices[i], 1, member->virtual_uuid,
                              MVGAL_UUID_TYPE_DEVICE_ID);
    }
    
    /* Aggregate properties */
    aggregate_device_properties(&g_device_group);
    aggregate_memory_properties(&g_device_group);
    aggregate_device_features(&g_device_group);
    
    /* Setup present capabilities */
    g_device_group.present_caps.sType = 
        VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_CAPABILITIES_KHR;
    g_device_group.present_caps.pNext = NULL;
    g_device_group.present_caps.presentMask[0] = g_device_group.present_device_mask;
    g_device_group.present_caps.modes = 
        VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR |
        VK_DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR |
        VK_DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR;
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    mvgal_log_info("Device group created with %u GPUs", gpu_count);
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_get_properties(VkPhysicalDeviceProperties *properties)
{
    if (!properties) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    *properties = g_device_group.aggregated_properties;
    pthread_mutex_unlock(&g_device_group.lock);
    
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_get_memory_properties(VkPhysicalDeviceMemoryProperties *memory)
{
    if (!memory) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    *memory = g_device_group.aggregated_memory;
    pthread_mutex_unlock(&g_device_group.lock);
    
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_get_features(VkPhysicalDeviceFeatures *features)
{
    if (!features) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    *features = g_device_group.aggregated_features;
    pthread_mutex_unlock(&g_device_group.lock);
    
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

mvgal_error_t mvgal_device_group_get_uuid(mvgal_uuid_type_t type, uint8_t *uuid)
{
    if (!uuid) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    switch (type) {
        case MVGAL_UUID_TYPE_PIPELINE_CACHE:
            memcpy(uuid, g_device_group.virtual_pipeline_cache_uuid, VK_UUID_SIZE);
            break;
        case MVGAL_UUID_TYPE_DRIVER_ID:
            memcpy(uuid, g_device_group.virtual_driver_uuid, VK_UUID_SIZE);
            break;
        case MVGAL_UUID_TYPE_DEVICE_ID:
            memcpy(uuid, g_device_group.virtual_device_uuid, VK_UUID_SIZE);
            break;
        default:
            pthread_mutex_unlock(&g_device_group.lock);
            pthread_mutex_unlock(&g_group_lock);
            return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

uint32_t mvgal_device_group_get_size(void)
{
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return 0;
    }
    
    uint32_t count = g_device_group.device_count;
    
    pthread_mutex_unlock(&g_group_lock);
    
    return count;
}

uint32_t mvgal_device_group_get_render_mask(void)
{
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return 0;
    }
    
    uint32_t mask = g_device_group.render_device_mask;
    
    pthread_mutex_unlock(&g_group_lock);
    
    return mask;
}

mvgal_error_t mvgal_device_group_get_present_capabilities(
    VkDeviceGroupPresentCapabilitiesKHR *caps)
{
    if (!caps) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    *caps = g_device_group.present_caps;
    pthread_mutex_unlock(&g_device_group.lock);
    
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

bool mvgal_device_group_is_peer_accessible(uint32_t src_gpu, uint32_t dst_gpu)
{
    if (src_gpu >= MAX_DEVICE_GROUP_SIZE || dst_gpu >= MAX_DEVICE_GROUP_SIZE) {
        return false;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized || src_gpu >= g_device_group.device_count ||
        dst_gpu >= g_device_group.device_count) {
        pthread_mutex_unlock(&g_group_lock);
        return false;
    }
    
    bool accessible = g_device_group.members[src_gpu].can_access_peers[dst_gpu];
    
    pthread_mutex_unlock(&g_group_lock);
    
    return accessible;
}

mvgal_error_t mvgal_device_group_set_peer_access(uint32_t src_gpu,
                                                  uint32_t dst_gpu,
                                                  bool accessible,
                                                  VkPeerMemoryFeatureFlags features)
{
    if (src_gpu >= MAX_DEVICE_GROUP_SIZE || dst_gpu >= MAX_DEVICE_GROUP_SIZE) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    if (src_gpu < g_device_group.device_count &&
        dst_gpu < g_device_group.device_count) {
        g_device_group.members[src_gpu].can_access_peers[dst_gpu] = accessible;
        g_device_group.members[src_gpu].peer_features[dst_gpu] = features;
    }
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

/* ============================================================================
 * Queue Family Aggregation
 * ============================================================================ */

/**
 * @brief Aggregate queue family properties from all group members
 *
 * This creates a unified view of queue families across all GPUs in the group.
 * Queue families with similar capabilities are merged.
 */
static void aggregate_queue_families(mvgal_device_group_t *group)
{
    uint32_t total_queue_families = 0;
    
    /* Count total queue families across all devices */
    for (uint32_t i = 0; i < group->device_count; i++) {
        total_queue_families += group->members[i].queue_family_count;
    }
    
    if (total_queue_families == 0) {
        /* If not yet populated, we use the real GPU properties */
        for (uint32_t i = 0; i < group->device_count; i++) {
            mvgal_device_group_member_t *member = &group->members[i];
            
            /* In a real ICD, we would call the underlying driver here.
             * For MVGAL, we proxy these calls.
             */
            uint32_t count = 0;
            mvgal_get_queue_family_properties(&count, NULL);
            
            if (count > 0) {
                member->queue_families = calloc(count, sizeof(VkQueueFamilyProperties));
                if (member->queue_families) {
                    mvgal_get_queue_family_properties(&count, member->queue_families);
                    member->queue_family_count = count;
                }
            }
        }
    }
}

/**
 * @brief Get aggregated queue family properties for the entire device group
 */
mvgal_error_t mvgal_device_group_get_queue_families(
    uint32_t *pCount,
    VkQueueFamilyProperties *pProperties)
{
    if (!pCount) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    /* Ensure queue families are aggregated */
    aggregate_queue_families(&g_device_group);
    
    /* Calculate total queue families across all devices */
    uint32_t total_families = 0;
    for (uint32_t i = 0; i < g_device_group.device_count; i++) {
        total_families += g_device_group.members[i].queue_family_count;
    }
    
    if (pProperties == NULL) {
        *pCount = total_families;
        pthread_mutex_unlock(&g_device_group.lock);
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_SUCCESS;
    }
    
    /* Copy queue family properties */
    uint32_t copied = 0;
    for (uint32_t i = 0; i < g_device_group.device_count && copied < *pCount; i++) {
        mvgal_device_group_member_t *member = &g_device_group.members[i];
        
        for (uint32_t j = 0; j < member->queue_family_count && copied < *pCount; j++) {
            pProperties[copied] = member->queue_families[j];
            copied++;
        }
    }
    
    *pCount = copied;
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

/* ============================================================================
 * VK_KHR_device_group_creation Support
 * ============================================================================ */

/**
 * @brief Enumerate physical device groups (for vkEnumeratePhysicalDeviceGroupsKHR)
 *
 * This is the key entry point for multi-GPU device group creation.
 * It returns a single device group containing all available GPUs.
 */
mvgal_error_t mvgal_enumerate_device_groups(
    VkPhysicalDeviceGroupPropertiesKHR *pProperties,
    uint32_t *pCount)
{
    if (!pCount) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        /* Auto-initialize with all available GPUs */
        pthread_mutex_unlock(&g_group_lock);
        
        int32_t gpu_count = mvgal_gpu_get_count();
        if (gpu_count <= 0) {
            *pCount = 0;
            return MVGAL_SUCCESS;
        }
        
        uint32_t *indices = malloc(gpu_count * sizeof(uint32_t));
        if (!indices) {
            return MVGAL_ERROR_OUT_OF_MEMORY;
        }
        
        for (int32_t i = 0; i < gpu_count; i++) {
            indices[i] = (uint32_t)i;
        }
        
        mvgal_error_t err = mvgal_device_group_init();
        if (err == MVGAL_SUCCESS) {
            err = mvgal_device_group_create(indices, (uint32_t)gpu_count, NULL);
        }
        
        free(indices);
        
        if (err != MVGAL_SUCCESS) {
            *pCount = 0;
            return err;
        }
        
        pthread_mutex_lock(&g_group_lock);
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    /* We return exactly one device group containing all GPUs */
    if (pProperties == NULL) {
        *pCount = 1;
        pthread_mutex_unlock(&g_device_group.lock);
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_SUCCESS;
    }
    
    /* Fill in the device group properties */
    pProperties[0].sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES_KHR;
    pProperties[0].pNext = NULL;
    pProperties[0].physicalDeviceCount = g_device_group.device_count;
    
    /* Copy physical device handles */
    for (uint32_t i = 0; i < g_device_group.device_count; i++) {
        pProperties[0].physicalDevices[i] = g_device_group.members[i].physical_device;
    }
    
    /* Device group has subset allocation if not all devices need to be used together */
    pProperties[0].subsetAllocation = VK_FALSE;
    
    *pCount = 1;
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

/**
 * @brief Create a device with device group support
 */
mvgal_error_t mvgal_device_group_create_device(
    const VkDeviceGroupDeviceCreateInfoKHR *pDeviceGroupInfo,
    const VkDeviceCreateInfo *pCreateInfo,
    VkDevice *pDevice)
{
    if (!pCreateInfo || !pDevice) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    /* Validate device group info if provided */
    if (pDeviceGroupInfo) {
        if (pDeviceGroupInfo->physicalDeviceCount != g_device_group.device_count) {
            pthread_mutex_unlock(&g_group_lock);
            mvgal_log_warn("Device group count mismatch: requested %u, have %u",
                     pDeviceGroupInfo->physicalDeviceCount,
                     g_device_group.device_count);
            return MVGAL_ERROR_INVALID_ARGUMENT;
        }
    }
    
    pthread_mutex_unlock(&g_group_lock);
    
    /* The actual device creation is handled by the ICD layer */
    /* We just validate the device group configuration here */
    
    mvgal_log_info("Device group creation validated for %u GPUs", 
                   g_device_group.device_count);
    
    return MVGAL_SUCCESS;
}

/* ============================================================================
 * Device Group Synchronization
 * ============================================================================ */

/**
 * @brief Get device group surface presentation modes
 */
mvgal_error_t mvgal_device_group_get_surface_present_modes(
    VkSurfaceKHR surface,
    VkDeviceGroupPresentModeFlagsKHR *pModes)
{
    if (!pModes) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    pthread_mutex_lock(&g_device_group.lock);
    
    /* Support all presentation modes:
     * - LOCAL: Present from the local device
     * - REMOTE: Present from another device  
     * - SUM: Sum of masks from all devices
     * - LOCAL_MULTI_DEVICE: Local device can present to multiple devices
     */
    *pModes = VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR |
             VK_DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR |
             VK_DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR |
             VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_MULTI_DEVICE_BIT_KHR;
    
    pthread_mutex_unlock(&g_device_group.lock);
    pthread_mutex_unlock(&g_group_lock);
    
    return MVGAL_SUCCESS;
}

/**
 * @brief Acquire next image with device group support
 */
mvgal_error_t mvgal_device_group_acquire_next_image(
    VkSwapchainKHR swapchain,
    uint64_t timeout,
    VkSemaphore semaphore,
    VkFence fence,
    uint32_t deviceMask,
    uint32_t *pImageIndex)
{
    if (!pImageIndex) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    /* Validate device mask */
    pthread_mutex_lock(&g_group_lock);
    
    if (!g_device_group.initialized) {
        pthread_mutex_unlock(&g_group_lock);
        return MVGAL_ERROR_NOT_INITIALIZED;
    }
    
    uint32_t valid_mask = (1U << g_device_group.device_count) - 1;
    
    pthread_mutex_unlock(&g_group_lock);
    
    if (deviceMask & ~valid_mask) {
        return MVGAL_ERROR_INVALID_ARGUMENT;
    }
    
    /* The actual acquire is handled by the presentation layer */
    /* We validate the device mask here */
    
    (void)swapchain;
    (void)timeout;
    (void)semaphore;
    (void)fence;
    
    return MVGAL_SUCCESS;
}

/**
 * @brief Create a cross-vendor synchronization semaphore
 *
 * Uses VK_KHR_external_semaphore to create a semaphore that can be shared
 * between different vendor drivers via file descriptors (OPAQUE_FD).
 */
mvgal_error_t mvgal_sync_create_cross_vendor_semaphore(
    VkDevice device,
    VkSemaphore *pSemaphore)
{
    VkExportSemaphoreCreateInfo exportInfo = {
        .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
        .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT
    };

    VkSemaphoreCreateInfo createInfo = {
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
        .pNext = &exportInfo
    };

    /* This would call the real vkCreateSemaphore on the underlying device */
    /* For now, we simulate the success */
    (void)device;
    *pSemaphore = (VkSemaphore)(uintptr_t)0x5E11AB0; 
    
    return MVGAL_SUCCESS;
}

/**
 * @brief Export a semaphore to a file descriptor
 */
int mvgal_sync_export_semaphore(VkDevice device, VkSemaphore semaphore)
{
    /* In a real implementation, this would use vkGetSemaphoreFdKHR */
    (void)device;
    (void)semaphore;
    return 42; /* Mock FD */
}

/**
 * @brief Import a semaphore from a file descriptor
 */
mvgal_error_t mvgal_sync_import_semaphore(VkDevice device, VkSemaphore semaphore, int fd)
{
    /* In a real implementation, this would use vkImportSemaphoreFdKHR */
    (void)device;
    (void)semaphore;
    (void)fd;
    return MVGAL_SUCCESS;
}
