//
//  HDRImageView.m
//  SnoopX
//
//  MTKView-based HDR display using Float16 textures and EDR
//

#import "HDRImageView.h"
#import <Metal/Metal.h>
#import <simd/simd.h>

// Half-float (IEEE 754 binary16) to float conversion (same as in Snooper.m)
static inline CGFloat halfFloatToFloat(uint16_t h) {
    uint32_t sign = (h & 0x8000) << 16;
    uint32_t exponent = (h & 0x7C00) >> 10;
    uint32_t mantissa = (h & 0x03FF);
    
    if (exponent == 0) {
        if (mantissa == 0) return 0.0f; // Zero
        // Denormalized number
        return (sign ? -1.0f : 1.0f) * powf(2.0f, -14.0f) * (mantissa / 1024.0f);
    } else if (exponent == 31) {
        // Infinity or NaN
        return (mantissa == 0) ? (sign ? -INFINITY : INFINITY) : NAN;
    }
    
    // Normalized number
    uint32_t floatBits = sign | (((exponent - 15 + 127) & 0xFF) << 23) | (mantissa << 13);
    return *((float *)&floatBits);
}

// Vertex structure for full-screen quad
typedef struct {
    vector_float2 position;
    vector_float2 texCoord;
} Vertex;

@interface HDRImageView ()
@property (nonatomic, strong) id<MTLDevice> metalDevice;
@property (nonatomic, strong) id<MTLCommandQueue> commandQueue;
@property (nonatomic, strong) id<MTLRenderPipelineState> pipelineState;
@property (nonatomic, strong) id<MTLTexture> currentTexture;
@property (nonatomic, strong) id<MTLBuffer> vertexBuffer;
@property (nonatomic, assign) CGFloat edrHeadroom;
@end

@implementation HDRImageView

- (instancetype)initWithFrame:(NSRect)frameRect {
    // Get default Metal device
    id<MTLDevice> device = MTLCreateSystemDefaultDevice();
    if (!device) {
        NSLog(@"❌ Metal is not supported on this device");
        return nil;
    }
    
    // Initialize MTKView with RGBA16Float pixel format
    self = [super initWithFrame:frameRect device:device];
    if (self) {
        [self setupMetal];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        // Set Metal device
        id<MTLDevice> device = MTLCreateSystemDefaultDevice();
        if (!device) {
            NSLog(@"❌ Metal is not supported on this device");
            return nil;
        }
        self.device = device;
        [self setupMetal];
    }
    return self;
}

- (void)setupMetal {
    self.metalDevice = self.device;
    
    // Configure MTKView for HDR
    self.colorPixelFormat = MTLPixelFormatRGBA16Float; // Float16 for HDR
    self.framebufferOnly = NO; // Allow reading from framebuffer if needed
    self.autoResizeDrawable = YES;
    self.enableSetNeedsDisplay = YES; // Manual rendering
    self.paused = YES; // Don't render continuously
    
    // Query EDR headroom from the screen
    if (@available(macOS 10.15, *)) {
        NSScreen *screen = self.window.screen ?: [NSScreen mainScreen];
        self.edrHeadroom = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
#ifdef SNOOPX_VERBOSE
        CGFloat currentEDR = screen.maximumExtendedDynamicRangeColorComponentValue;
        NSLog(@"🔆 Screen EDR - Potential: %.2f, Current: %.2f", self.edrHeadroom, currentEDR);
#endif
    } else {
        self.edrHeadroom = 1.0;
#ifdef SNOOPX_VERBOSE
        NSLog(@"⚠️ EDR not available (macOS < 10.15)");
#endif
    }
    
    // Enable EDR (Extended Dynamic Range)
    CAMetalLayer *metalLayer = (CAMetalLayer *)self.layer;
    metalLayer.wantsExtendedDynamicRangeContent = YES;

    // Set color space for display interpretation of Float16 texture data
    // Default to gamma-encoded Extended Display P3 (matches monitor appearance).
    // The useLinearDisplayColorSpace property can switch this to linear for unclamped EDR viewing.
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedDisplayP3);
    if (!colorSpace) {
        colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
    }
    metalLayer.colorspace = colorSpace;
    CGColorSpaceRelease(colorSpace);

    // Set pixel format
    metalLayer.pixelFormat = MTLPixelFormatRGBA16Float;

#ifdef SNOOPX_VERBOSE
    NSLog(@"✅ HDR Metal layer configured: EDR=%d, colorSpace=ExtendedLinearDisplayP3, pixelFormat=RGBA16Float",
          metalLayer.wantsExtendedDynamicRangeContent);
#endif
    
    // Create command queue
    self.commandQueue = [self.metalDevice newCommandQueue];
    
    // Set delegate
    self.delegate = self;
    
    // Create render pipeline
    [self setupPipeline];
    
    // Create vertex buffer for full-screen quad
    [self setupVertexBuffer];
    
    self.enableHDR = YES;
}

- (void)setupPipeline {
    NSError *error = nil;
    
    // Metal shader source (inline)
    NSString *shaderSource = @
    "#include <metal_stdlib>\n"
    "using namespace metal;\n"
    "\n"
    "struct VertexIn {\n"
    "    float2 position [[attribute(0)]];\n"
    "    float2 texCoord [[attribute(1)]];\n"
    "};\n"
    "\n"
    "struct VertexOut {\n"
    "    float4 position [[position]];\n"
    "    float2 texCoord;\n"
    "};\n"
    "\n"
    "vertex VertexOut vertexShader(VertexIn in [[stage_in]]) {\n"
    "    VertexOut out;\n"
    "    out.position = float4(in.position, 0.0, 1.0);\n"
    "    out.texCoord = in.texCoord;\n"
    "    return out;\n"
    "}\n"
    "\n"
    "fragment half4 fragmentShader(VertexOut in [[stage_in]],\n"
    "                              texture2d<half, access::sample> texture [[texture(0)]]) {\n"
    "    constexpr sampler textureSampler(mag_filter::nearest, min_filter::nearest);\n"
    "    \n"
    "    // Sample and pass through - no modifications\n"
    "    // EDR system will handle values > 1.0 automatically\n"
    "    half4 color = texture.sample(textureSampler, in.texCoord);\n"
    "    \n"
    "    return color;\n"
    "}\n";
    
    // Compile shader
    id<MTLLibrary> library = [self.metalDevice newLibraryWithSource:shaderSource
                                                            options:nil
                                                              error:&error];
    if (!library) {
        NSLog(@"❌ Failed to compile Metal shader: %@", error);
        return;
    }
    
    id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"];
    id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"];
    
    // Create vertex descriptor
    MTLVertexDescriptor *vertexDescriptor = [[MTLVertexDescriptor alloc] init];
    
    // Position attribute
    vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2;
    vertexDescriptor.attributes[0].offset = 0;
    vertexDescriptor.attributes[0].bufferIndex = 0;
    
    // TexCoord attribute
    vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
    vertexDescriptor.attributes[1].offset = sizeof(vector_float2);
    vertexDescriptor.attributes[1].bufferIndex = 0;
    
    // Layout
    vertexDescriptor.layouts[0].stride = sizeof(Vertex);
    vertexDescriptor.layouts[0].stepRate = 1;
    vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
    
    // Create pipeline descriptor
    MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    pipelineDescriptor.vertexFunction = vertexFunction;
    pipelineDescriptor.fragmentFunction = fragmentFunction;
    pipelineDescriptor.vertexDescriptor = vertexDescriptor;
    pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA16Float;
    
    // Create pipeline state
    self.pipelineState = [self.metalDevice newRenderPipelineStateWithDescriptor:pipelineDescriptor
                                                                           error:&error];
    if (!self.pipelineState) {
        NSLog(@"❌ Failed to create render pipeline state: %@", error);
        return;
    }
    
#ifdef SNOOPX_VERBOSE
    NSLog(@"✅ Metal render pipeline created (pass-through for EDR)");
#endif
}

- (void)setupVertexBuffer {
    // Full-screen quad vertices (normalized device coordinates)
    // Position: [-1, 1] range, TexCoord: [0, 1] range
    Vertex vertices[] = {
        // Position        TexCoord
        {{-1.0,  1.0},   {0.0, 0.0}},  // Top-left
        {{-1.0, -1.0},   {0.0, 1.0}},  // Bottom-left
        {{ 1.0, -1.0},   {1.0, 1.0}},  // Bottom-right
        
        {{-1.0,  1.0},   {0.0, 0.0}},  // Top-left
        {{ 1.0, -1.0},   {1.0, 1.0}},  // Bottom-right
        {{ 1.0,  1.0},   {1.0, 0.0}},  // Top-right
    };
    
    self.vertexBuffer = [self.metalDevice newBufferWithBytes:vertices
                                                      length:sizeof(vertices)
                                                     options:MTLResourceStorageModeShared];
}

#pragma mark - Image Property

- (void)setImage:(NSImage *)image {
    _image = image;

    if (image) {
        // Convert NSImage to Metal texture
        self.currentTexture = [self createTextureFromImage:image];

        // Trigger redraw - use draw instead of setNeedsDisplay for immediate update
        [self draw];
    } else {
        self.currentTexture = nil;
        [self draw];
    }
}

- (void)setCGImage:(CGImageRef CF_RELEASES_ARGUMENT)cgImage {
    _image = nil; // Clear NSImage when using CGImage directly

    if (cgImage) {
        // Create texture directly from CGImage (preserves HDR)
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
        size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

        // Check if this is Float16 HDR data
        CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
        BOOL isFloat = (bitmapInfo & kCGBitmapFloatComponents) != 0;
        BOOL isFloat16 = isFloat && (bitsPerComponent == 16);

#ifdef SNOOPX_VERBOSE
        {
            CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(cgImage);
            CFStringRef colorSpaceName = CGColorSpaceCopyName(imageColorSpace);
            NSLog(@"📊 setCGImage: %zux%zu Float16=%d colorSpace=%@", width, height, isFloat16, (__bridge NSString *)colorSpaceName);
            if (colorSpaceName) CFRelease(colorSpaceName);
        }
#else
        (void)width; (void)height; (void)bitsPerComponent; (void)bitsPerPixel;
#endif

        if (isFloat16) {
            self.currentTexture = [self createTextureFromFloat16CGImage:cgImage];
        } else {
            self.currentTexture = [self createTextureFromNonFloat16CGImage:cgImage];
        }

        // Release the CGImage (CF_RELEASES_ARGUMENT)
        CGImageRelease(cgImage);

        // Trigger redraw
        [self draw];
    } else {
        self.currentTexture = nil;
        [self draw];
    }
}

- (id<MTLTexture>)createTextureFromImage:(NSImage *)image {
    if (!image) return nil;
    
    // Try multiple methods to get the HDR CGImage
    CGImageRef cgImage = nil;
    
    // Method 1: Check all representations for CGImage-based reps
    NSArray *representations = [image representations];
    for (NSImageRep *rep in representations) {
#ifdef SNOOPX_VERBOSE
        NSLog(@"🔍 Checking representation: %@", [rep class]);
#endif
        // Try NSBitmapImageRep
        if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
            cgImage = [(NSBitmapImageRep *)rep CGImage];
#ifdef SNOOPX_VERBOSE
            NSLog(@"📸 Got CGImage from NSBitmapImageRep");
#endif
            break;
        }

        // Try NSCGImageSnapshotRep (private but might work)
        if ([rep respondsToSelector:@selector(cgImage)]) {
            // performSelector returns id, but we know it's CGImageRef
            id result = [rep performSelector:@selector(cgImage)];
            if (result) {
                cgImage = (__bridge CGImageRef)result;
#ifdef SNOOPX_VERBOSE
                NSLog(@"📸 Got CGImage from cgImage property");
#endif
                break;
            }
        }

        // Generic CGImage method
        if ([rep respondsToSelector:@selector(CGImage)]) {
            cgImage = (__bridge CGImageRef)[rep performSelector:@selector(CGImage)];
            if (cgImage) {
#ifdef SNOOPX_VERBOSE
                NSLog(@"📸 Got CGImage via CGImage selector");
#endif
                break;
            }
        }
    }

    // Method 2: Use CGImageForProposedRect as last resort
    if (!cgImage) {
#ifdef SNOOPX_VERBOSE
        NSLog(@"⚠️ No direct CGImage found, using CGImageForProposedRect");
#endif
        cgImage = [image CGImageForProposedRect:NULL context:nil hints:nil];
    }
    
    if (!cgImage) {
        NSLog(@"❌ Failed to get CGImage from NSImage");
        return nil;
    }
    
    size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
#ifdef SNOOPX_VERBOSE
    {
        size_t width = CGImageGetWidth(cgImage);
        size_t height = CGImageGetHeight(cgImage);
        size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
        NSLog(@"🖼️ Creating texture from image: %zux%zu, %zu bpc, %zu bpp", width, height, bitsPerComponent, bitsPerPixel);
        CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(cgImage);
        CFStringRef colorSpaceName = CGColorSpaceCopyName(imageColorSpace);
        NSLog(@"🎨 Image color space: %@", (__bridge NSString *)colorSpaceName);
        if (colorSpaceName) CFRelease(colorSpaceName);
    }
#endif

    // Check if this is Float16 HDR data
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
    BOOL isFloat = (bitmapInfo & kCGBitmapFloatComponents) != 0;
    BOOL isFloat16 = isFloat && (bitsPerComponent == 16);

#ifdef SNOOPX_VERBOSE
    NSLog(@"📊 Image format: isFloat=%d, isFloat16=%d, bitmapInfo=0x%x", isFloat, isFloat16, bitmapInfo);
#endif

    if (isFloat16) {
#ifdef SNOOPX_VERBOSE
        NSLog(@"✨ Source is Float16 HDR - direct texture upload");
#endif
        return [self createTextureFromFloat16CGImage:cgImage];
    } else if (_enableHDR) {
#ifdef SNOOPX_VERBOSE
        NSLog(@"⚠️ Source is NOT Float16 but HDR enabled - converting via Float32");
#endif
        return [self createTextureFromNonFloat16CGImage:cgImage];
    } else {
#ifdef SNOOPX_VERBOSE
        NSLog(@"✅ SDR mode - using simple 8-bit texture path");
#endif
        return [self createTextureFrom8BitCGImage:cgImage];
    }
}

// Simple 8-bit SDR path - use native sRGB texture format for correct gamma
- (id<MTLTexture>)createTextureFrom8BitCGImage:(CGImageRef)cgImage {
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);

#ifdef SNOOPX_VERBOSE
    NSLog(@"📦 Simple SDR path: %zux%zu", width, height);
#endif

    // Get the raw 8-bit BGRA data from CGImage
    CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(provider);
    const uint8_t *rgba8Data = (const uint8_t *)CFDataGetBytePtr(data);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);

    // Check format - we expect BGRA with premultiplied alpha
    BOOL isBGRA = (bitmapInfo & kCGBitmapByteOrder32Little) != 0;
#ifdef SNOOPX_VERBOSE
    NSLog(@"📦 Source: bytesPerRow=%zu, bitmapInfo=0x%x", bytesPerRow, bitmapInfo);
    NSLog(@"📦 Format: %s", isBGRA ? "BGRA" : "RGBA");
#endif

    // Use BGRA8Unorm_sRGB for proper sRGB gamma handling - this is key for correct colors!
    // The _sRGB suffix tells Metal the data is gamma-encoded, not linear
    MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm_sRGB
                                                                                                 width:width
                                                                                                height:height
                                                                                             mipmapped:NO];
    textureDescriptor.usage = MTLTextureUsageShaderRead;
    textureDescriptor.storageMode = MTLStorageModeManaged;

    id<MTLTexture> texture = [self.metalDevice newTextureWithDescriptor:textureDescriptor];
    if (!texture) {
        NSLog(@"❌ Failed to create Metal texture");
        CFRelease(data);
        return nil;
    }

    // Copy 8-bit BGRA data directly - no conversion needed!
    // Just need to ensure we're copying the right bytes
    if (isBGRA && bytesPerRow == width * 4) {
        // Perfect match - direct copy
        MTLRegion region = MTLRegionMake2D(0, 0, width, height);
        [texture replaceRegion:region
                   mipmapLevel:0
                     withBytes:rgba8Data
                   bytesPerRow:bytesPerRow];
#ifdef SNOOPX_VERBOSE
        NSLog(@"✅ Direct copy - BGRA format matches");
#endif
    } else {
        // Need to reformat - copy row by row
#ifdef SNOOPX_VERBOSE
        NSLog(@"⚠️ Reformatting texture data");
#endif
        uint8_t *textureData = (uint8_t *)malloc(width * height * 4);

        for (size_t y = 0; y < height; y++) {
            const uint8_t *srcRow = rgba8Data + y * bytesPerRow;
            uint8_t *dstRow = textureData + y * width * 4;

            for (size_t x = 0; x < width; x++) {
                const uint8_t *src = srcRow + x * 4;
                uint8_t *dst = dstRow + x * 4;

                if (isBGRA) {
                    // Already BGRA - direct copy
                    dst[0] = src[0]; // B
                    dst[1] = src[1]; // G
                    dst[2] = src[2]; // R
                    dst[3] = src[3]; // A
                } else {
                    // RGBA -> BGRA
                    dst[0] = src[2]; // B
                    dst[1] = src[1]; // G
                    dst[2] = src[0]; // R
                    dst[3] = src[3]; // A
                }
            }
        }

        MTLRegion region = MTLRegionMake2D(0, 0, width, height);
        [texture replaceRegion:region
                   mipmapLevel:0
                     withBytes:textureData
                   bytesPerRow:width * 4];

        free(textureData);
    }

    CFRelease(data);

#ifdef SNOOPX_VERBOSE
    NSLog(@"✅ Created SDR texture: %zux%zu with sRGB gamma", width, height);
#endif

    return texture;
}

// Direct upload from Float16 CGImage (preserves HDR perfectly)
- (id<MTLTexture>)createTextureFromFloat16CGImage:(CGImageRef)cgImage {
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    
    // Get the raw Float16 data from CGImage
    CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(provider);
    const uint16_t *float16Data = (const uint16_t *)CFDataGetBytePtr(data);
    size_t dataLength = CFDataGetLength(data);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
    
#ifdef SNOOPX_VERBOSE
    NSLog(@"📦 Float16 data: %zu bytes for %zux%zu image (bytesPerRow: %zu, expected: %zu)",
          dataLength, width, height, bytesPerRow, width * 4 * sizeof(uint16_t));
    {
        CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
        NSLog(@"📊 Bitmap info: 0x%x, alpha: %u, byteOrder: 0x%x",
              bitmapInfo, alphaInfo, bitmapInfo & kCGBitmapByteOrderMask);
    }
    if (width > 0 && height > 0) {
        for (int y = 0; y < MIN(3, (int)height); y++) {
            for (int x = 0; x < MIN(5, (int)width); x++) {
                size_t rowStart = y * (bytesPerRow / sizeof(uint16_t));
                size_t idx = rowStart + x * 4;
                float r = halfFloatToFloat(float16Data[idx]);
                float g = halfFloatToFloat(float16Data[idx + 1]);
                float b = halfFloatToFloat(float16Data[idx + 2]);
                float a = halfFloatToFloat(float16Data[idx + 3]);
                NSLog(@"🔬 Pixel[%d,%d] (Float16→Float32): R=%.3f G=%.3f B=%.3f A=%.3f",
                      x, y, r, g, b, a);
            }
        }
    }
#else
    (void)dataLength;
#endif
    
    // Create Metal texture descriptor
    MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA16Float
                                                                                                 width:width
                                                                                                height:height
                                                                                             mipmapped:NO];
    textureDescriptor.usage = MTLTextureUsageShaderRead;
    textureDescriptor.storageMode = MTLStorageModeManaged;
    
    id<MTLTexture> texture = [self.metalDevice newTextureWithDescriptor:textureDescriptor];
    if (!texture) {
        NSLog(@"❌ Failed to create Metal texture");
        CFRelease(data);
        return nil;
    }
    
    // Upload Float16 data directly to texture
    // Use the actual bytes per row from the image, not the calculated one
    MTLRegion region = MTLRegionMake2D(0, 0, width, height);
    [texture replaceRegion:region
               mipmapLevel:0
                 withBytes:float16Data
               bytesPerRow:bytesPerRow];  // Use actual bytesPerRow from CGImage
    
    CFRelease(data);
    
    return texture;
}

// Fallback for non-Float16 images (8-bit, etc.)
- (id<MTLTexture>)createTextureFromNonFloat16CGImage:(CGImageRef)cgImage {
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    
#ifdef SNOOPX_VERBOSE
    CGColorSpaceRef srcColorSpace = CGImageGetColorSpace(cgImage);
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
    NSLog(@"🔍 Source image: %zux%zu, alpha=%d", width, height, alphaInfo);
#endif

    // Create Float32 RGBA bitmap context (as intermediate step)
    // Use Extended Linear sRGB for HDR, regular sRGB for SDR
    CGColorSpaceRef workingColorSpace;
    if (_enableHDR) {
#ifdef SNOOPX_VERBOSE
        NSLog(@"🎨 HDR mode: using Extended Linear sRGB");
#endif
        workingColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
    } else {
#ifdef SNOOPX_VERBOSE
        NSLog(@"🎨 SDR mode: using regular sRGB");
#endif
        workingColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    }

    size_t bytesPerRow = width * 4 * sizeof(float);
    float *bitmapData = (float *)calloc(height * width * 4, sizeof(float));
    
    // Initialize to opaque white for debugging
    for (size_t i = 0; i < height * width; i++) {
        bitmapData[i * 4 + 0] = 1.0f; // R
        bitmapData[i * 4 + 1] = 1.0f; // G
        bitmapData[i * 4 + 2] = 1.0f; // B
        bitmapData[i * 4 + 3] = 1.0f; // A
    }
    
    CGContextRef context = CGBitmapContextCreate(
        bitmapData,
        width,
        height,
        32, // 32 bits per component (Float32)
        bytesPerRow,
        workingColorSpace,
        kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents
    );
    
    CGColorSpaceRelease(workingColorSpace);
    
    if (!context) {
        NSLog(@"❌ Failed to create Float32 CGContext");
        free(bitmapData);
        return nil;
    }
    
#ifdef SNOOPX_VERBOSE
    if (_enableHDR) {
        NSLog(@"🎨 Drawing 8-bit image into Float32 Extended Linear sRGB context");
    } else {
        NSLog(@"🎨 Drawing 8-bit image into Float32 sRGB context");
    }
#endif

    // Draw image into Float32 context
    // Core Graphics will handle color space conversion if needed
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(context);
    
#ifdef SNOOPX_VERBOSE
    // Log sample pixel values to verify conversion worked
    if (width > 0 && height > 0) {
        size_t centerX = width / 2;
        size_t centerY = height / 2;
        size_t idx = (centerY * width + centerX) * 4;
        NSLog(@"🔬 Center pixel (Float32): R=%.3f G=%.3f B=%.3f A=%.3f",
              bitmapData[idx], bitmapData[idx+1], bitmapData[idx+2], bitmapData[idx+3]);
    }
#endif
    
    // Create Metal texture descriptor (RGBA16Float for HDR)
    MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA16Float
                                                                                                 width:width
                                                                                                height:height
                                                                                             mipmapped:NO];
    textureDescriptor.usage = MTLTextureUsageShaderRead;
    textureDescriptor.storageMode = MTLStorageModeManaged;
    
    id<MTLTexture> texture = [self.metalDevice newTextureWithDescriptor:textureDescriptor];
    if (!texture) {
        NSLog(@"❌ Failed to create Metal texture");
        free(bitmapData);
        return nil;
    }
    
    // Convert Float32 to Float16 and upload to texture
    // Metal expects half-float (16-bit float) data
    size_t pixelCount = width * height;
    uint16_t *float16Data = (uint16_t *)malloc(pixelCount * 4 * sizeof(uint16_t));
    
    // Use vImage or manual conversion for Float32 -> Float16
    // For simplicity, we'll use a manual conversion here
    for (size_t i = 0; i < pixelCount * 4; i++) {
        float16Data[i] = [self floatToHalf:bitmapData[i]];
    }
    
    free(bitmapData);
    
    // Upload Float16 data to texture
    MTLRegion region = MTLRegionMake2D(0, 0, width, height);
    [texture replaceRegion:region
               mipmapLevel:0
                 withBytes:float16Data
               bytesPerRow:width * 4 * sizeof(uint16_t)];
    
    free(float16Data);
    
#ifdef SNOOPX_VERBOSE
    NSLog(@"✅ Created RGBA16Float Metal texture: %zux%zu", width, height);
#endif
    
    return texture;
}

// Float32 to Float16 conversion using standard IEEE 754 half-float format
- (uint16_t)floatToHalf:(float)value {
    uint32_t floatBits = *((uint32_t *)&value);
    
    uint32_t sign = (floatBits >> 16) & 0x8000;
    int32_t exponent = ((floatBits >> 23) & 0xFF) - 127 + 15;
    uint32_t mantissa = (floatBits >> 13) & 0x3FF;
    
    // Handle special cases
    if (exponent <= 0) {
        // Zero or denormalized
        if (exponent < -10) {
            return (uint16_t)sign; // Too small, return zero
        }
        // Denormalized number
        mantissa = (mantissa | 0x400) >> (1 - exponent);
        return (uint16_t)(sign | mantissa);
    } else if (exponent >= 31) {
        // Infinity or NaN
        return (uint16_t)(sign | 0x7C00 | (mantissa ? 0x3FF : 0));
    }
    
    // Normalized number
    return (uint16_t)(sign | (exponent << 10) | mantissa);
}

#pragma mark - MTKViewDelegate

- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
#ifdef SNOOPX_VERBOSE
    NSLog(@"🔄 Drawable size changed: %.0fx%.0f", size.width, size.height);
#endif
}

- (void)drawInMTKView:(MTKView *)view {
#ifdef SNOOPX_VERBOSE
    NSLog(@"🎨 drawInMTKView called, texture=%@", self.currentTexture);
#endif
    
    @autoreleasepool {
        // Get command buffer
        id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
        if (!commandBuffer) {
            NSLog(@"❌ Failed to create command buffer");
            return;
        }
        
        // Get render pass descriptor
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        if (!renderPassDescriptor) {
            NSLog(@"❌ No render pass descriptor");
            return;
        }
        
        // Clear to black
        renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
        
        // Create render command encoder
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        
        if (self.currentTexture && self.pipelineState) {
#ifdef SNOOPX_VERBOSE
            NSLog(@"🎨 Drawing textured quad (texture size: %lux%lu, EDR headroom: %.2f)",
                  (unsigned long)[self.currentTexture width],
                  (unsigned long)[self.currentTexture height],
                  self.edrHeadroom);
#endif
            
            // Draw textured quad with pass-through shader
            [renderEncoder setRenderPipelineState:self.pipelineState];
            [renderEncoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
            [renderEncoder setFragmentTexture:self.currentTexture atIndex:0];
            [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];
        } else {
#ifdef SNOOPX_VERBOSE
            NSLog(@"⚠️ No texture or pipeline state - clearing to black");
#endif
        }
        
        [renderEncoder endEncoding];
        
        // Present drawable
        id<CAMetalDrawable> drawable = view.currentDrawable;
        if (drawable) {
            [commandBuffer presentDrawable:drawable];
            [commandBuffer commit];
#ifdef SNOOPX_VERBOSE
            NSLog(@"✅ Frame presented");
#endif
        } else {
            NSLog(@"❌ No drawable available");
        }
    }
}

- (void)setEnableHDR:(BOOL)enableHDR {
    _enableHDR = enableHDR;

    // Update Metal layer EDR setting
    CAMetalLayer *metalLayer = (CAMetalLayer *)self.layer;
    metalLayer.wantsExtendedDynamicRangeContent = enableHDR;

#ifdef SNOOPX_VERBOSE
    NSLog(@"🎨 HDR %@", enableHDR ? @"enabled" : @"disabled");
#endif
}

- (void)setUseLinearDisplayColorSpace:(BOOL)useLinear {
    _useLinearDisplayColorSpace = useLinear;

    // Update Metal layer color space to change how Float16 texture data is interpreted
    CAMetalLayer *metalLayer = (CAMetalLayer *)self.layer;
    CGColorSpaceRef cs;
    if (useLinear) {
        cs = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
    } else {
        cs = CGColorSpaceCreateWithName(kCGColorSpaceExtendedDisplayP3);
    }
    metalLayer.colorspace = cs;
    CGColorSpaceRelease(cs);

#ifndef NDEBUG
    NSLog(@"[HDRImageView] layer colorspace = %s",
          useLinear ? "ExtendedLinearDisplayP3" : "ExtendedDisplayP3");
#endif
}

@end
