// MobileGL - MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
//   https://www.gnu.org/licenses/gpl-3.0.txt
//   https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header

#include "CGLImpl.h"

#if defined(__APPLE__)
#include "../EGLImpl/EGLImpl.h"

namespace MobileGL::MG_Impl::CGLImpl {
    namespace {
        struct PixelFormatObject {
            Uint32 RetainCount = 1;
            Bool DoubleBuffer = true;
            GLint ColorSize = 24;
            GLint AlphaSize = 8;
            GLint DepthSize = 24;
            GLint StencilSize = 8;
            GLint SampleBuffers = 0;
            GLint Samples = 0;
            GLint Profile = kCGLOGLPVersion_3_2_Core;
            GLint RendererId = 0x4d474c;
            GLint DisplayMask = 0;
        };

        struct ContextObject {
            Uint32 RetainCount = 1;
            CGLPixelFormatObj PixelFormat = nullptr;
            CGLContextObj Share = nullptr;
            EGLDisplay Display = EGL_NO_DISPLAY;
            EGLConfig Config = nullptr;
            EGLContext Context = EGL_NO_CONTEXT;
            EGLSurface Surface = EGL_NO_SURFACE;
            void* NSObject = nullptr;
            void* View = nullptr;
            void* MetalLayer = nullptr;
            GLint SwapInterval = 1;
            GLint VirtualScreen = 0;
            GLint SurfaceBackingSize[2] = {0, 0};
            Bool HasDrawable = false;
            Bool Locked = false;
        };

        std::recursive_mutex& RegistryMutex() {
            static auto* mutex = new std::recursive_mutex();
            return *mutex;
        }

        Uint64& NextPixelFormatHandle() {
            static auto* handle = new Uint64(1);
            return *handle;
        }

        Uint64& NextContextHandle() {
            static auto* handle = new Uint64(1);
            return *handle;
        }

        UnorderedMap<CGLPixelFormatObj, PixelFormatObject>& PixelFormats() {
            static auto* formats = new UnorderedMap<CGLPixelFormatObj, PixelFormatObject>();
            return *formats;
        }

        UnorderedMap<CGLContextObj, ContextObject>& Contexts() {
            static auto* contexts = new UnorderedMap<CGLContextObj, ContextObject>();
            return *contexts;
        }

        UnorderedMap<std::thread::id, CGLContextObj>& CurrentContexts() {
            static auto* contexts = new UnorderedMap<std::thread::id, CGLContextObj>();
            return *contexts;
        }

        CGLPixelFormatObj EncodePixelFormat(Uint64 handle) {
            return reinterpret_cast<CGLPixelFormatObj>(static_cast<SizeT>(handle));
        }

        CGLContextObj EncodeContext(Uint64 handle) {
            return reinterpret_cast<CGLContextObj>(static_cast<SizeT>(handle));
        }

        std::thread::id CurrentThreadKey() {
            return std::this_thread::get_id();
        }

        Bool AttributeHasValue(CGLPixelFormatAttribute attrib) {
            switch (attrib) {
            case kCGLPFAColorSize:
            case kCGLPFAAlphaSize:
            case kCGLPFADepthSize:
            case kCGLPFAStencilSize:
            case kCGLPFASampleBuffers:
            case kCGLPFASamples:
            case kCGLPFARendererID:
            case kCGLPFADisplayMask:
            case kCGLPFAOpenGLProfile:
                return true;
            default:
                return false;
            }
        }

        void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat,
                                       CGLPixelFormatAttribute attrib,
                                       GLint value) {
            switch (attrib) {
            case kCGLPFADoubleBuffer:
                pixelFormat.DoubleBuffer = true;
                break;
            case kCGLPFAColorSize:
                pixelFormat.ColorSize = value;
                break;
            case kCGLPFAAlphaSize:
                pixelFormat.AlphaSize = value;
                break;
            case kCGLPFADepthSize:
                pixelFormat.DepthSize = value;
                break;
            case kCGLPFAStencilSize:
                pixelFormat.StencilSize = value;
                break;
            case kCGLPFASampleBuffers:
                pixelFormat.SampleBuffers = value;
                break;
            case kCGLPFASamples:
                pixelFormat.Samples = value;
                break;
            case kCGLPFAOpenGLProfile:
                pixelFormat.Profile = value;
                break;
            case kCGLPFARendererID:
                pixelFormat.RendererId = value;
                break;
            case kCGLPFADisplayMask:
                pixelFormat.DisplayMask = value;
                break;
            default:
                break;
            }
        }

        Bool InitEGLContext(ContextObject& object, CGLPixelFormatObj pix, CGLContextObj share) {
            auto* pixelFormat = [&]() -> PixelFormatObject* {
                auto& pixelFormats = PixelFormats();
                auto it = pixelFormats.find(pix);
                return it == pixelFormats.end() ? nullptr : &it->second;
            }();
            if (!pixelFormat) {
                return false;
            }

            EGLDisplay display = EGLImpl::GetDisplay(EGL_DEFAULT_DISPLAY);
            if (display == EGL_NO_DISPLAY) {
                return false;
            }
            if (!EGLImpl::Initialize(display, nullptr, nullptr)) {
                return false;
            }
            EGLImpl::BindAPI(EGL_OPENGL_API);

            const EGLint attribs[] = {
                EGL_RED_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_BLUE_SIZE, 8,
                EGL_ALPHA_SIZE, std::max(pixelFormat->AlphaSize, 0),
                EGL_DEPTH_SIZE, std::max(pixelFormat->DepthSize, 0),
                EGL_STENCIL_SIZE, std::max(pixelFormat->StencilSize, 0),
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
                EGL_NONE,
            };

            EGLConfig config = nullptr;
            EGLint count = 0;
            if (!EGLImpl::ChooseConfig(display, attribs, &config, 1, &count) || count <= 0) {
                return false;
            }

            EGLContext shareContext = EGL_NO_CONTEXT;
            if (share != nullptr) {
                auto& contexts = Contexts();
                auto shareIt = contexts.find(share);
                if (shareIt == contexts.end()) {
                    return false;
                }
                shareContext = shareIt->second.Context;
            }

            const EGLint contextAttribs[] = {
                EGL_CONTEXT_MAJOR_VERSION, 3,
                EGL_CONTEXT_MINOR_VERSION, 3,
                EGL_NONE,
            };
            EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs);
            if (eglContext == EGL_NO_CONTEXT) {
                return false;
            }

            object.Display = display;
            object.Config = config;
            object.Context = eglContext;
            object.PixelFormat = pix;
            object.Share = share;
            return true;
        }

        ContextObject* TryGetContext(CGLContextObj ctx) {
            auto& contexts = Contexts();
            auto it = contexts.find(ctx);
            return it == contexts.end() ? nullptr : &it->second;
        }

        const ContextObject* TryGetContext(CGLContextObj ctx, const std::lock_guard<std::recursive_mutex>&) {
            auto& contexts = Contexts();
            auto it = contexts.find(ctx);
            return it == contexts.end() ? nullptr : &it->second;
        }

        PixelFormatObject* TryGetPixelFormat(CGLPixelFormatObj pix) {
            auto& pixelFormats = PixelFormats();
            auto it = pixelFormats.find(pix);
            return it == pixelFormats.end() ? nullptr : &it->second;
        }

        CGLError MakeCurrentLocked(CGLContextObj ctx, ContextObject& object) {
            CurrentContexts()[CurrentThreadKey()] = ctx;
            if (!object.HasDrawable || object.Surface == EGL_NO_SURFACE) {
                return kCGLNoError;
            }
            if (!EGLImpl::MakeCurrent(object.Display, object.Surface, object.Surface, object.Context)) {
                return kCGLBadState;
            }
            return kCGLNoError;
        }

        CGLError RecreateSurfaceLocked(CGLContextObj ctx, ContextObject& object) {
            if (!object.MetalLayer) {
                return kCGLBadDrawable;
            }
            if (object.Surface != EGL_NO_SURFACE) {
                EGLImpl::DestroySurface(object.Display, object.Surface);
                object.Surface = EGL_NO_SURFACE;
            }
            const EGLAttrib attribs[] = {
                EGL_WIDTH, std::max<GLint>(object.SurfaceBackingSize[0], 1),
                EGL_HEIGHT, std::max<GLint>(object.SurfaceBackingSize[1], 1),
                EGL_NONE,
            };
            EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config,
                                                                      object.MetalLayer, attribs);
            if (surface == EGL_NO_SURFACE) {
                object.HasDrawable = false;
                return kCGLBadDrawable;
            }
            object.Surface = surface;
            object.HasDrawable = true;
            return GetCurrentContext() == ctx ? MakeCurrentLocked(ctx, object) : kCGLNoError;
        }

        CGLError ResizeSurfaceLocked(ContextObject& object) {
            if (object.Surface == EGL_NO_SURFACE) {
                return kCGLBadDrawable;
            }
            return EGLImpl::ResizePlatformWindowSurface(
                       object.Display, object.Surface,
                       std::max<GLint>(object.SurfaceBackingSize[0], 1),
                       std::max<GLint>(object.SurfaceBackingSize[1], 1))
                       ? kCGLNoError
                       : kCGLBadDrawable;
        }
    } // namespace

    CGLError ChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        if (!pix || !npix) {
            return kCGLBadAddress;
        }

        PixelFormatObject object;
        if (attribs) {
            for (SizeT i = 0; attribs[i] != static_cast<CGLPixelFormatAttribute>(0); ++i) {
                const auto attrib = attribs[i];
                GLint value = 1;
                if (AttributeHasValue(attrib)) {
                    value = static_cast<GLint>(attribs[++i]);
                }
                ApplyPixelFormatAttribute(object, attrib, value);
            }
        }

        const auto handle = EncodePixelFormat(NextPixelFormatHandle()++);
        PixelFormats()[handle] = object;
        *pix = handle;
        *npix = 1;
        return kCGLNoError;
    }

    CGLError DestroyPixelFormat(CGLPixelFormatObj pix) {
        ReleasePixelFormat(pix);
        return kCGLNoError;
    }

    CGLError DescribePixelFormat(CGLPixelFormatObj pix, GLint pixNum, CGLPixelFormatAttribute attrib, GLint* value) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        if (!value) {
            return kCGLBadAddress;
        }
        if (pixNum != 0 && pixNum != 1) {
            return kCGLBadValue;
        }
        auto* pixelFormat = TryGetPixelFormat(pix);
        if (!pixelFormat) {
            return kCGLBadPixelFormat;
        }

        switch (attrib) {
        case kCGLPFADoubleBuffer:
            *value = pixelFormat->DoubleBuffer ? 1 : 0;
            return kCGLNoError;
        case kCGLPFAAccelerated:
        case kCGLPFAAcceleratedCompute:
        case kCGLPFASupportsAutomaticGraphicsSwitching:
            *value = 1;
            return kCGLNoError;
        case kCGLPFAColorSize:
            *value = pixelFormat->ColorSize;
            return kCGLNoError;
        case kCGLPFAAlphaSize:
            *value = pixelFormat->AlphaSize;
            return kCGLNoError;
        case kCGLPFADepthSize:
            *value = pixelFormat->DepthSize;
            return kCGLNoError;
        case kCGLPFAStencilSize:
            *value = pixelFormat->StencilSize;
            return kCGLNoError;
        case kCGLPFASampleBuffers:
            *value = pixelFormat->SampleBuffers;
            return kCGLNoError;
        case kCGLPFASamples:
            *value = pixelFormat->Samples;
            return kCGLNoError;
        case kCGLPFARendererID:
            *value = pixelFormat->RendererId;
            return kCGLNoError;
        case kCGLPFADisplayMask:
            *value = pixelFormat->DisplayMask;
            return kCGLNoError;
        case kCGLPFAOpenGLProfile:
            *value = pixelFormat->Profile;
            return kCGLNoError;
        case kCGLPFAVirtualScreenCount:
            *value = 1;
            return kCGLNoError;
        default:
            *value = 0;
            return kCGLNoError;
        }
    }

    void ReleasePixelFormat(CGLPixelFormatObj pix) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* pixelFormat = TryGetPixelFormat(pix);
        if (!pixelFormat) {
            return;
        }
        if (pixelFormat->RetainCount > 1) {
            --pixelFormat->RetainCount;
            return;
        }
        PixelFormats().erase(pix);
    }

    CGLPixelFormatObj RetainPixelFormat(CGLPixelFormatObj pix) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* pixelFormat = TryGetPixelFormat(pix);
        if (pixelFormat) {
            ++pixelFormat->RetainCount;
        }
        return pix;
    }

    GLuint GetPixelFormatRetainCount(CGLPixelFormatObj pix) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* pixelFormat = TryGetPixelFormat(pix);
        return pixelFormat ? pixelFormat->RetainCount : 0;
    }

    CGLError CreateContext(CGLPixelFormatObj pix, CGLContextObj share, CGLContextObj* ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        if (!ctx) {
            return kCGLBadAddress;
        }
        if (!TryGetPixelFormat(pix)) {
            return kCGLBadPixelFormat;
        }
        if (share && !TryGetContext(share)) {
            return kCGLBadMatch;
        }

        ContextObject object;
        if (!InitEGLContext(object, pix, share)) {
            return kCGLBadAlloc;
        }
        RetainPixelFormat(pix);
        const auto handle = EncodeContext(NextContextHandle()++);
        Contexts()[handle] = object;
        *ctx = handle;
        return kCGLNoError;
    }

    CGLError DestroyContext(CGLContextObj ctx) {
        ReleaseContext(ctx);
        return kCGLNoError;
    }

    CGLContextObj RetainContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (object) {
            ++object->RetainCount;
        }
        return ctx;
    }

    void ReleaseContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return;
        }
        if (object->RetainCount > 1) {
            --object->RetainCount;
            return;
        }
        if (object->Surface != EGL_NO_SURFACE) {
            EGLImpl::DestroySurface(object->Display, object->Surface);
        }
        if (object->Context != EGL_NO_CONTEXT) {
            EGLImpl::DestroyContext(object->Display, object->Context);
        }
        ReleasePixelFormat(object->PixelFormat);
        auto& currentContexts = CurrentContexts();
        for (auto it = currentContexts.begin(); it != currentContexts.end();) {
            if (it->second == ctx) {
                it = currentContexts.erase(it);
            } else {
                ++it;
            }
        }
        Contexts().erase(ctx);
    }

    GLuint GetContextRetainCount(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        return object ? object->RetainCount : 0;
    }

    CGLPixelFormatObj GetPixelFormat(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        return object ? object->PixelFormat : nullptr;
    }

    CGLError SetCurrentContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        if (!ctx) {
            CurrentContexts().erase(CurrentThreadKey());
            EGLImpl::MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            return kCGLNoError;
        }
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        return MakeCurrentLocked(ctx, *object);
    }

    CGLContextObj GetCurrentContext() {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto& currentContexts = CurrentContexts();
        auto it = currentContexts.find(CurrentThreadKey());
        return it == currentContexts.end() ? nullptr : it->second;
    }

    CGLError SetVirtualScreen(CGLContextObj ctx, GLint screen) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (screen != 0) {
            return kCGLBadValue;
        }
        object->VirtualScreen = screen;
        return kCGLNoError;
    }

    CGLError GetVirtualScreen(CGLContextObj ctx, GLint* screen) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (!screen) {
            return kCGLBadAddress;
        }
        *screen = object->VirtualScreen;
        return kCGLNoError;
    }

    CGLError SetParameter(CGLContextObj ctx, CGLContextParameter pname, const GLint* params) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (!params && pname != kCGLCPReclaimResources) {
            return kCGLBadAddress;
        }
        switch (pname) {
        case kCGLCPSwapInterval:
            object->SwapInterval = params[0];
            EGLImpl::SwapInterval(object->Display, object->SwapInterval);
            return kCGLNoError;
        case kCGLCPSurfaceBackingSize:
        {
            const GLint width = std::max<GLint>(params[0], 1);
            const GLint height = std::max<GLint>(params[1], 1);
            if (object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height) {
                return kCGLNoError;
            }
            object->SurfaceBackingSize[0] = width;
            object->SurfaceBackingSize[1] = height;
            if (object->MetalLayer && object->Surface != EGL_NO_SURFACE) {
                return ResizeSurfaceLocked(*object);
            }
            return kCGLNoError;
        }
        case kCGLCPSurfaceOpacity:
        case kCGLCPSurfaceOrder:
        case kCGLCPMPSwapsInFlight:
        case kCGLCPReclaimResources:
            return kCGLNoError;
        default:
            return kCGLNoError;
        }
    }

    CGLError GetParameter(CGLContextObj ctx, CGLContextParameter pname, GLint* params) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (!params) {
            return kCGLBadAddress;
        }
        switch (pname) {
        case kCGLCPSwapInterval:
            params[0] = object->SwapInterval;
            return kCGLNoError;
        case kCGLCPSurfaceBackingSize:
            params[0] = object->SurfaceBackingSize[0];
            params[1] = object->SurfaceBackingSize[1];
            return kCGLNoError;
        case kCGLCPCurrentRendererID:
            params[0] = 0x4d474c;
            return kCGLNoError;
        case kCGLCPGPUVertexProcessing:
        case kCGLCPGPUFragmentProcessing:
        case kCGLCPHasDrawable:
            params[0] = object->HasDrawable ? 1 : 0;
            return kCGLNoError;
        case kCGLCPMPSwapsInFlight:
            params[0] = 1;
            return kCGLNoError;
        default:
            params[0] = 0;
            return kCGLNoError;
        }
    }

    CGLError UpdateContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        return TryGetContext(ctx) ? kCGLNoError : kCGLBadContext;
    }

    CGLError ClearDrawable(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (object->Surface != EGL_NO_SURFACE) {
            EGLImpl::DestroySurface(object->Display, object->Surface);
        }
        object->Surface = EGL_NO_SURFACE;
        object->View = nullptr;
        object->MetalLayer = nullptr;
        object->HasDrawable = false;
        return kCGLNoError;
    }

    CGLError FlushDrawable(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (!object->HasDrawable || object->Surface == EGL_NO_SURFACE) {
            return kCGLBadDrawable;
        }
        const auto currentError = MakeCurrentLocked(ctx, *object);
        if (currentError != kCGLNoError) {
            return currentError;
        }
        return EGLImpl::SwapBuffers(object->Display, object->Surface) ? kCGLNoError : kCGLBadDrawable;
    }

    CGLError LockContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        object->Locked = true;
        return kCGLNoError;
    }

    CGLError UnlockContext(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        object->Locked = false;
        return kCGLNoError;
    }

    void GetVersion(GLint* majorvers, GLint* minorvers) {
        if (majorvers) {
            *majorvers = 1;
        }
        if (minorvers) {
            *minorvers = 0;
        }
    }

    const char* ErrorString(CGLError error) {
        switch (error) {
        case kCGLNoError:
            return "no error";
        case kCGLBadAttribute:
            return "invalid pixel format attribute";
        case kCGLBadPixelFormat:
            return "invalid pixel format";
        case kCGLBadContext:
            return "invalid context";
        case kCGLBadDrawable:
            return "invalid drawable";
        case kCGLBadState:
            return "invalid context state";
        case kCGLBadValue:
            return "invalid numerical value";
        case kCGLBadMatch:
            return "invalid share context";
        case kCGLBadAddress:
            return "invalid pointer";
        case kCGLBadAlloc:
            return "invalid memory allocation";
        default:
            return "unknown CGL error";
        }
    }

    CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (!object) {
            return kCGLBadContext;
        }
        if (!metalLayer) {
            return kCGLBadDrawable;
        }
        width = std::max<GLint>(width, 1);
        height = std::max<GLint>(height, 1);
        const Bool sameSize = object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height;
        object->SurfaceBackingSize[0] = width;
        object->SurfaceBackingSize[1] = height;
        if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer && sameSize) {
            object->View = nsView;
            object->HasDrawable = true;
            return kCGLNoError;
        }
        if (object->Surface != EGL_NO_SURFACE && object->MetalLayer == metalLayer) {
            object->View = nsView;
            object->HasDrawable = true;
            return ResizeSurfaceLocked(*object);
        }
        if (object->Surface != EGL_NO_SURFACE) {
            EGLImpl::DestroySurface(object->Display, object->Surface);
            object->Surface = EGL_NO_SURFACE;
        }
        object->View = nsView;
        object->MetalLayer = metalLayer;
        const auto recreateError = RecreateSurfaceLocked(ctx, *object);
        if (recreateError != kCGLNoError) {
            return recreateError;
        }
        return kCGLNoError;
    }

    void* GetContextNSObject(CGLContextObj ctx) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        return object ? object->NSObject : nullptr;
    }

    void SetContextNSObject(CGLContextObj ctx, void* nsObject) {
        const std::lock_guard<std::recursive_mutex> lock(RegistryMutex());
        auto* object = TryGetContext(ctx);
        if (object) {
            object->NSObject = nsObject;
        }
    }
} // namespace MobileGL::MG_Impl::CGLImpl
#endif
