// Copyright (c) Wojciech Figat. All rights reserved.

#if USE_NETCORE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using FlaxEngine.Utilities;

#pragma warning disable 1591

namespace FlaxEngine.Interop
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct NativeClassDefinitions
    {
        internal ManagedHandle typeHandle;
        internal IntPtr nativePointer;
        internal IntPtr name;
        internal IntPtr fullname;
        internal IntPtr @namespace;
        internal uint typeAttributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct NativeMethodDefinitions
    {
        internal IntPtr name;
        internal int numParameters;
        internal ManagedHandle typeHandle;
        internal uint methodAttributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct NativeFieldDefinitions
    {
        internal IntPtr name;
        internal ManagedHandle fieldHandle;
        internal ManagedHandle fieldTypeHandle;
        internal int fieldOffset;
        internal uint fieldAttributes;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct NativePropertyDefinitions
    {
        internal IntPtr name;
        internal ManagedHandle propertyHandle;
        internal ManagedHandle getterHandle;
        internal ManagedHandle setterHandle;
        internal uint getterAttributes;
        internal uint setterAttributes;
    }

    unsafe partial class NativeInterop
    {
        [LibraryImport("FlaxEngine", EntryPoint = "NativeInterop_CreateClass", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
        internal static partial void NativeInterop_CreateClass(ref NativeClassDefinitions managedClass, ManagedHandle assemblyHandle);

        internal enum MTypes : uint
        {
            End = 0x00,
            Void = 0x01,
            Boolean = 0x02,
            Char = 0x03,
            I1 = 0x04,
            U1 = 0x05,
            I2 = 0x06,
            U2 = 0x07,
            I4 = 0x08,
            U4 = 0x09,
            I8 = 0x0a,
            U8 = 0x0b,
            R4 = 0x0c,
            R8 = 0x0d,
            String = 0x0e,
            Ptr = 0x0f,
            ByRef = 0x10,
            ValueType = 0x11,
            Class = 0x12,
            Var = 0x13,
            Array = 0x14,
            GenericInst = 0x15,
            TypeByRef = 0x16,
            I = 0x18,
            U = 0x19,
            Fnptr = 0x1b,
            Object = 0x1c,
            SzArray = 0x1d,
            MVar = 0x1e,
            CmodReqd = 0x1f,
            CmodOpt = 0x20,
            Internal = 0x21,
            Modifier = 0x40,
            Sentinel = 0x41,
            Pinned = 0x45,
            Enum = 0x55,
        };

        [UnmanagedCallersOnly]
        internal static void RegisterNativeLibrary(IntPtr moduleNamePtr, IntPtr modulePathPtr)
        {
            string moduleName = Marshal.PtrToStringAnsi(moduleNamePtr);
            string modulePath = Marshal.PtrToStringUni(modulePathPtr);
            libraryPaths[moduleName] = modulePath;
        }

        [UnmanagedCallersOnly]
        internal static void* AllocMemory(int size, bool coTaskMem)
        {
            if (coTaskMem)
                return Marshal.AllocCoTaskMem(size).ToPointer();
            else
                return NativeMemory.AlignedAlloc((UIntPtr)size, 16);
        }

        [UnmanagedCallersOnly]
        internal static void FreeMemory(void* ptr, bool coTaskMem)
        {
            if (coTaskMem)
                Marshal.FreeCoTaskMem(new IntPtr(ptr));
            else
                NativeMemory.AlignedFree(ptr);
        }

        private static Assembly GetOwningAssembly(Type type)
        {
            Assembly assembly = null;
            if (type.IsGenericType && !type.Assembly.IsCollectible)
            {
                // The owning assembly of a generic type with type arguments referencing
                // collectible assemblies must be one of the collectible assemblies.
                foreach (var genericType in type.GetGenericArguments())
                {
                    if (genericType.Assembly.IsCollectible)
                    {
                        assembly = genericType.Assembly;
                        break;
                    }
                }
            }
            if (assembly == null)
                assembly = type.Assembly;
            return assembly;
        }

        private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, out ManagedHandle assemblyHandle)
        {
            assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type));
            return CreateNativeClassDefinitions(type);
        }

        private static NativeClassDefinitions CreateNativeClassDefinitions(Type type)
        {
            return new NativeClassDefinitions()
            {
                typeHandle = RegisterType(type).handle,
                name = NativeAllocStringAnsi(type.Name),
                fullname = NativeAllocStringAnsi(type.GetTypeName()),
                @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
                typeAttributes = (uint)type.Attributes,
            };
        }

        private static NativeClassDefinitions CreateNativeClassDefinitions(Type type, ManagedHandle typeHandle, out ManagedHandle assemblyHandle)
        {
            assemblyHandle = GetAssemblyHandle(GetOwningAssembly(type));
            return new NativeClassDefinitions()
            {
                typeHandle = typeHandle,
                name = NativeAllocStringAnsi(type.Name),
                fullname = NativeAllocStringAnsi(type.GetTypeName()),
                @namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
                typeAttributes = (uint)type.Attributes,
            };
        }

        [UnmanagedCallersOnly]
        internal static void GetManagedClasses(ManagedHandle assemblyHandle, NativeClassDefinitions** managedClasses, int* managedClassCount)
        {
            Assembly assembly = Unsafe.As<Assembly>(assemblyHandle.Target);
            Type[] assemblyTypes = GetAssemblyTypes(assembly);

            *managedClasses = (NativeClassDefinitions*)NativeAlloc(assemblyTypes.Length, Unsafe.SizeOf<NativeClassDefinitions>());
            *managedClassCount = assemblyTypes.Length;
            Span<NativeClassDefinitions> span = new Span<NativeClassDefinitions>(*managedClasses, assemblyTypes.Length);
            for (int i = 0; i < assemblyTypes.Length; i++)
            {
                Type type = assemblyTypes[i];
                ref var managedClass = ref span[i];
                managedClass = CreateNativeClassDefinitions(type);
            }
        }

        [UnmanagedCallersOnly]
        internal static void RegisterManagedClassNativePointers(NativeClassDefinitions** managedClasses, int managedClassCount)
        {
            Span<NativeClassDefinitions> span = new Span<NativeClassDefinitions>(Unsafe.Read<IntPtr>(managedClasses).ToPointer(), managedClassCount);
            foreach (ref NativeClassDefinitions managedClass in span)
            {
                TypeHolder typeHolder = Unsafe.As<TypeHolder>(managedClass.typeHandle.Target);
                typeHolder.managedClassPointer = managedClass.nativePointer;
            }
        }

        [UnmanagedCallersOnly]
        internal static void GetManagedClassFromType(ManagedHandle typeHandle, NativeClassDefinitions* managedClass, ManagedHandle* assemblyHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            *managedClass = CreateNativeClassDefinitions(type, out ManagedHandle handle);
            *assemblyHandle = handle;
        }

        private static void RegisterNativeClassFromType(TypeHolder typeHolder, ManagedHandle typeHandle)
        {
            NativeClassDefinitions managedClass = CreateNativeClassDefinitions(typeHolder.type, typeHandle, out ManagedHandle assemblyHandle);
            NativeInterop_CreateClass(ref managedClass, assemblyHandle);
            typeHolder.managedClassPointer = managedClass.nativePointer;
        }

        [UnmanagedCallersOnly]
        internal static void GetClassMethods(ManagedHandle typeHandle, NativeMethodDefinitions** classMethods, int* classMethodsCount)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);

            var methods = new List<MethodInfo>();
            var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            var instanceMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
            methods.AddRange(staticMethods);
            methods.AddRange(instanceMethods);

            var arr = (NativeMethodDefinitions*)NativeAlloc(methods.Count, Unsafe.SizeOf<NativeMethodDefinitions>());
            for (int i = 0; i < methods.Count; i++)
            {
                var method = methods[i];
                IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf<NativeMethodDefinitions>() * i);
                var classMethod = new NativeMethodDefinitions
                {
                    name = NativeAllocStringAnsi(method.Name),
                    numParameters = method.GetParameters().Length,
                    methodAttributes = (uint)method.Attributes,
                };
                classMethod.typeHandle = GetMethodGCHandle(method);
                Unsafe.Write(ptr.ToPointer(), classMethod);
            }
            *classMethods = arr;
            *classMethodsCount = methods.Count;
        }

        [UnmanagedCallersOnly]
        internal static void GetClassFields(ManagedHandle typeHandle, NativeFieldDefinitions** classFields, int* classFieldsCount)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            var fields = type.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (type.IsValueType && !type.IsEnum && !type.IsPrimitive && type.GetCustomAttribute<System.Runtime.CompilerServices.IsByRefLikeAttribute>() != null)
                fields = Array.Empty<FieldInfo>(); // ref struct are not supported

            NativeFieldDefinitions* arr = (NativeFieldDefinitions*)NativeAlloc(fields.Length, Unsafe.SizeOf<NativeFieldDefinitions>());
            for (int i = 0; i < fields.Length; i++)
            {
                FieldHolder fieldHolder = new FieldHolder(fields[i], type);

                ManagedHandle fieldHandle = ManagedHandle.Alloc(fieldHolder);
#if FLAX_EDITOR
                if (type.IsCollectible)
                    fieldHandleCacheCollectible.Add(fieldHandle);
                else
#endif
                {
                    fieldHandleCache.Add(fieldHandle);
                }

                NativeFieldDefinitions classField = new NativeFieldDefinitions()
                {
                    name = NativeAllocStringAnsi(fieldHolder.field.Name),
                    fieldHandle = fieldHandle,
                    fieldTypeHandle = GetTypeManagedHandle(fieldHolder.field.FieldType),
                    fieldOffset = fieldHolder.fieldOffset,
                    fieldAttributes = (uint)fieldHolder.field.Attributes,
                };
                Unsafe.Write(IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf<NativeFieldDefinitions>() * i).ToPointer(), classField);
            }
            *classFields = arr;
            *classFieldsCount = fields.Length;
        }

        [UnmanagedCallersOnly]
        internal static void GetClassProperties(ManagedHandle typeHandle, NativePropertyDefinitions** classProperties, int* classPropertiesCount)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            var properties = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            var arr = (NativePropertyDefinitions*)NativeAlloc(properties.Length, Unsafe.SizeOf<NativePropertyDefinitions>());
            for (int i = 0; i < properties.Length; i++)
            {
                var property = properties[i];

                ManagedHandle propertyHandle = ManagedHandle.Alloc(property);
#if FLAX_EDITOR
                if (type.IsCollectible)
                    propertyHandleCacheCollectible.Add(propertyHandle);
                else
#endif
                {
                    propertyHandleCache.Add(propertyHandle);
                }

                IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf<NativePropertyDefinitions>() * i);

                var getterMethod = property.GetGetMethod(true);
                var setterMethod = property.GetSetMethod(true);

                var classProperty = new NativePropertyDefinitions
                {
                    name = NativeAllocStringAnsi(property.Name),
                    propertyHandle = propertyHandle,
                };
                if (getterMethod != null)
                {
                    classProperty.getterHandle = GetMethodGCHandle(getterMethod);
                    classProperty.getterAttributes = (uint)getterMethod.Attributes;
                }
                if (setterMethod != null)
                {
                    classProperty.setterHandle = GetMethodGCHandle(setterMethod);
                    classProperty.setterAttributes = (uint)setterMethod.Attributes;
                }
                Unsafe.Write(ptr.ToPointer(), classProperty);
            }
            *classProperties = arr;
            *classPropertiesCount = properties.Length;
        }

        internal static void GetAttributes(object[] attributeValues, ManagedHandle** classAttributes, int* classAttributesCount)
        {
            ManagedHandle* arr = (ManagedHandle*)NativeAlloc(attributeValues.Length, Unsafe.SizeOf<ManagedHandle>());
            for (int i = 0; i < attributeValues.Length; i++)
            {
                if (!classAttributesCacheCollectible.TryGetValue(attributeValues[i], out ManagedHandle attributeHandle))
                {
                    attributeHandle = ManagedHandle.Alloc(attributeValues[i]);
                    classAttributesCacheCollectible.Add(attributeValues[i], attributeHandle);
                }
                arr[i] = attributeHandle;
            }
            *classAttributes = arr;
            *classAttributesCount = attributeValues.Length;
        }

        [UnmanagedCallersOnly]
        internal static void GetClassAttributes(ManagedHandle typeHandle, ManagedHandle** classAttributes, int* classAttributesCount)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            object[] attributeValues = type.GetCustomAttributes(false);
            GetAttributes(attributeValues, classAttributes, classAttributesCount);
        }

        [UnmanagedCallersOnly]
        internal static void GetMethodAttributes(ManagedHandle methodHandle, ManagedHandle** classAttributes, int* classAttributesCount)
        {
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
            object[] attributeValues = methodHolder.method.GetCustomAttributes(false);
            GetAttributes(attributeValues, classAttributes, classAttributesCount);
        }

        [UnmanagedCallersOnly]
        internal static void GetFieldAttributes(ManagedHandle fieldHandle, ManagedHandle** classAttributes, int* classAttributesCount)
        {
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            object[] attributeValues = field.field.GetCustomAttributes(false);
            GetAttributes(attributeValues, classAttributes, classAttributesCount);
        }

        [UnmanagedCallersOnly]
        internal static void GetPropertyAttributes(ManagedHandle propertyHandle, ManagedHandle** classAttributes, int* classAttributesCount)
        {
            PropertyInfo property = Unsafe.As<PropertyInfo>(propertyHandle.Target);
            object[] attributeValues = property.GetCustomAttributes(false);
            GetAttributes(attributeValues, classAttributes, classAttributesCount);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetCustomAttribute(ManagedHandle typeHandle, ManagedHandle attributeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            var attributes = type.GetCustomAttributes(false);
            object attrib;
            if (attributeHandle.IsAllocated)
            {
                // Check for certain attribute type
                Type attributeType = Unsafe.As<TypeHolder>(attributeHandle.Target);
                attrib = attributes.FirstOrDefault(x => x.GetType() == attributeType);
            }
            else
            {
                // Check if has any attribute
                attrib = attributes.FirstOrDefault();
            }
            if (attrib != null)
            {
                if (!classAttributesCacheCollectible.TryGetValue(attrib, out var handle))
                {
                    handle = ManagedHandle.Alloc(attrib);
                    classAttributesCacheCollectible.Add(attrib, handle);
                }
                return handle;
            }
            return new ManagedHandle();
        }

        [UnmanagedCallersOnly]
        internal static void GetClassInterfaces(ManagedHandle typeHandle, IntPtr* classInterfaces, int* classInterfacesCount)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            Type[] interfaces = type.GetInterfaces();

            // Match mono_class_get_interfaces which doesn't return interfaces from base class
            var baseTypeInterfaces = type.BaseType?.GetInterfaces();
            if (baseTypeInterfaces != null)
            {
                if (baseTypeInterfaces.Length == interfaces.Length)
                {
                    // Both base and this type have the same amount of interfaces so assume that this one has none unique
                    interfaces = Array.Empty<Type>();
                }
                else if (baseTypeInterfaces.Length != 0)
                {
                    // Count unique interface types
                    int uniqueCount = interfaces.Length;
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        for (int j = 0; j < baseTypeInterfaces.Length; j++)
                        {
                            if (interfaces[i] == baseTypeInterfaces[j])
                            {
                                uniqueCount--;
                                break;
                            }
                        }
                    }

                    // Get only unique interface types
                    var unique = new Type[uniqueCount];
                    uniqueCount = 0;
                    for (int i = 0; i < interfaces.Length; i++)
                    {
                        bool isUnique = true;
                        for (int j = 0; j < baseTypeInterfaces.Length; j++)
                        {
                            if (interfaces[i] == baseTypeInterfaces[j])
                            {
                                isUnique = false;
                                break;
                            }
                        }
                        if (isUnique)
                            unique[uniqueCount++] = interfaces[i];
                    }
                    interfaces = unique;
                }
            }

            IntPtr arr = (IntPtr)NativeAlloc(interfaces.Length, IntPtr.Size);
            for (int i = 0; i < interfaces.Length; i++)
            {
                ManagedHandle handle = GetTypeManagedHandle(interfaces[i]);
                Unsafe.Write<ManagedHandle>(IntPtr.Add(arr, IntPtr.Size * i).ToPointer(), handle);
            }
            *classInterfaces = arr;
            *classInterfacesCount = interfaces.Length;
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetMethodReturnType(ManagedHandle methodHandle)
        {
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
            Type returnType = methodHolder.returnType;
            return GetTypeManagedHandle(returnType);
        }

        [UnmanagedCallersOnly]
        internal static void GetMethodParameterTypes(ManagedHandle methodHandle, IntPtr* typeHandles)
        {
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
            Type[] parameterTypes = methodHolder.parameterTypes;
            IntPtr arr = (IntPtr)NativeAlloc(parameterTypes.Length, IntPtr.Size);
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                ManagedHandle typeHandle = GetTypeManagedHandle(parameterTypes[i]);
                Unsafe.Write<ManagedHandle>(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle);
            }
            *typeHandles = arr;
        }

        /// <summary>
        /// Returns pointer to the string's internal structure, containing the buffer and length of the string.
        /// </summary>
        [UnmanagedCallersOnly]
        internal static char* GetStringPointer(ManagedHandle stringHandle, int* len)
        {
            string str = Unsafe.As<string>(stringHandle.Target);
            *len = str.Length;
            fixed (char* p = str)
                return p;
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle NewObject(ManagedHandle typeHandle)
        {
            TypeHolder typeHolder = Unsafe.As<TypeHolder>(typeHandle.Target);
            try
            {
                object value = typeHolder.CreateObject();
                return ManagedHandle.Alloc(value);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
            return new ManagedHandle();
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size)
        {
            Type elementType = Unsafe.As<TypeHolder>(typeHandle.Target);
            Type marshalledType = ArrayFactory.GetMarshalledType(elementType);
            Type arrayType = ArrayFactory.GetArrayType(elementType);
            if (marshalledType.IsValueType)
            {
                ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, arrayType, marshalledType);
                return ManagedHandle.Alloc(managedArray);
            }
            else
            {
                Array arr = ArrayFactory.CreateArray(elementType, size);
                ManagedArray managedArray = ManagedArray.WrapNewArray(arr, arrayType);
                return ManagedHandle.Alloc(managedArray);
            }
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle)
        {
            Type elementType = Unsafe.As<TypeHolder>(elementTypeHandle.Target);
            Type arrayType = ArrayFactory.GetArrayType(elementType);
            return GetTypeManagedHandle(arrayType);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetArrayTypeFromWrappedArray(ManagedHandle arrayHandle)
        {
            ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
            Type elementType = managedArray.ArrayType.GetElementType();
            Type arrayType = ArrayFactory.GetArrayType(elementType);
            return GetTypeManagedHandle(arrayType);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetArrayPointer(ManagedHandle arrayHandle)
        {
            if (!arrayHandle.IsAllocated)
                return IntPtr.Zero;
            ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
            if (managedArray.Length == 0)
                return IntPtr.Zero;
            return managedArray.Pointer;
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetArray(ManagedHandle handle)
        {
            if (!handle.IsAllocated)
                return IntPtr.Zero;
            object value = handle.Target;
            if (value is ManagedArray)
                return (IntPtr)handle;
            if (value is Array)
                return Invoker.MarshalReturnValueGeneric(value.GetType(), value);
            return IntPtr.Zero;
        }

        [UnmanagedCallersOnly]
        internal static int GetArrayLength(ManagedHandle arrayHandle)
        {
            if (!arrayHandle.IsAllocated)
                return 0;
            ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
            return managedArray.Length;
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetStringEmpty()
        {
            return ManagedHandle.ToIntPtr(ManagedString.EmptyStringHandle);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr NewStringUTF16(char* text, int length)
        {
            return ManagedString.ToNativeWeak(new string(new ReadOnlySpan<char>(text, length)));
        }

        [UnmanagedCallersOnly]
        internal static IntPtr NewStringUTF8(sbyte* text, int length)
        {
            return ManagedString.ToNativeWeak(new string(text, 0, length, System.Text.Encoding.UTF8));
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetObjectType(ManagedHandle handle)
        {
            object obj = handle.Target;
            Type classType = obj.GetType();
            if (classType == typeof(ManagedArray))
                classType = ((ManagedArray)obj).ArrayType;
            return GetTypeManagedHandle(classType);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetObjectString(ManagedHandle handle)
        {
            object obj = handle.Target;
            string result = string.Empty;
            try
            {
                result = obj.ToString();
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
            return ManagedHandle.ToIntPtr(result);
        }

        [UnmanagedCallersOnly]
        internal static int GetObjectHashCode(ManagedHandle handle)
        {
            object obj = handle.Target;
            int result = 0;
            try
            {
                result = obj.GetHashCode();
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
            return result;
        }

        /// <summary>
        /// Creates a managed copy of the value, and stores it in a boxed reference.
        /// </summary>
        [UnmanagedCallersOnly]
        internal static ManagedHandle BoxValue(ManagedHandle typeHandle, IntPtr valuePtr)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            object value = MarshalToManaged(valuePtr, type);
            return ManagedHandle.Alloc(value, GCHandleType.Weak);
        }

        /// <summary>
        /// Returns the address of the boxed value type.
        /// </summary>
        [UnmanagedCallersOnly]
        internal static IntPtr UnboxValue(ManagedHandle handle)
        {
            object value = handle.Target;
            Type type = value.GetType();
            if (!type.IsValueType)
                return ManagedHandle.ToIntPtr(handle);
            return ValueTypeUnboxer.GetPointer(value, type);
        }

        [UnmanagedCallersOnly]
        internal static void RaiseException(ManagedHandle exceptionHandle)
        {
            Exception exception = Unsafe.As<Exception>(exceptionHandle.Target);
            throw exception;
        }

        [UnmanagedCallersOnly]
        internal static void ObjectInit(ManagedHandle objectHandle)
        {
            try
            {
                object obj = objectHandle.Target;
                Type type = obj.GetType();
                ConstructorInfo ctor = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
                if (ctor == null)
                    throw new Exception($"Missing empty constructor in type '{type}'.");
                ctor.Invoke(obj, null);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetObjectClass(ManagedHandle objectHandle)
        {
            object obj = objectHandle.Target;
            TypeHolder typeHolder = GetTypeHolder(obj.GetType());
            return typeHolder.managedClassPointer;
        }

        [UnmanagedCallersOnly]
        internal static IntPtr InvokeMethod(ManagedHandle instanceHandle, ManagedHandle methodHandle, IntPtr paramPtr, IntPtr exceptionPtr)
        {
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
#if !USE_AOT
            if (methodHolder.TryGetDelegate(out var methodDelegate, out var methodDelegateContext))
            {
                // Fast path, invoke the method with minimal allocations
                IntPtr returnValue;
                try
                {
                    returnValue = methodDelegate(methodDelegateContext, instanceHandle, paramPtr);
                }
                catch (Exception exception)
                {
                    if (exceptionPtr != IntPtr.Zero)
                        Unsafe.Write<IntPtr>(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(exception, GCHandleType.Weak));
                    return IntPtr.Zero;
                }
                return returnValue;
            }
            else
#endif
            {
                // Slow path, method parameters needs to be stored in heap
                object returnObject;
                int numParams = methodHolder.parameterTypes.Length;
                object[] methodParameters = new object[numParams];

                for (int i = 0; i < numParams; i++)
                {
                    IntPtr nativePtr = Unsafe.Read<IntPtr>((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
                    methodParameters[i] = MarshalToManaged(nativePtr, methodHolder.parameterTypes[i]);
                }

                try
                {
                    returnObject = methodHolder.method.Invoke(instanceHandle.IsAllocated ? instanceHandle.Target : null, methodParameters);
                }
                catch (Exception exception)
                {
                    // The internal exception thrown in MethodInfo.Invoke is caught here
                    Exception realException = exception;
                    if (exception.InnerException != null && exception.TargetSite.ReflectedType.Name == "MethodInvoker")
                        realException = exception.InnerException;

                    if (exceptionPtr != IntPtr.Zero)
                        Unsafe.Write<IntPtr>(exceptionPtr.ToPointer(), ManagedHandle.ToIntPtr(realException, GCHandleType.Weak));
                    else
                        throw realException;
                    return IntPtr.Zero;
                }

                // Marshal reference parameters back to original unmanaged references
                for (int i = 0; i < numParams; i++)
                {
                    Type parameterType = methodHolder.parameterTypes[i];
                    if (parameterType.IsByRef)
                    {
                        IntPtr nativePtr = Unsafe.Read<IntPtr>((IntPtr.Add(paramPtr, sizeof(IntPtr) * i)).ToPointer());
                        MarshalToNative(methodParameters[i], nativePtr, parameterType.GetElementType());
                    }
                }

                // Return value
                return Invoker.MarshalReturnValueGeneric(methodHolder.returnType, returnObject);
            }
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetThunk(ManagedHandle methodHandle)
        {
#if USE_AOT
            Debug.LogError("GetThunk is not supported in C# AOT mode");
            return IntPtr.Zero;
#else
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);

            // Wrap the method call, this is needed to get the object instance from ManagedHandle and to pass the exception back to native side
            ThunkContext context = new ThunkContext((MethodInfo)methodHolder.method);
            Delegate methodDelegate = typeof(ThunkContext).GetMethod(nameof(ThunkContext.InvokeThunk)).CreateDelegate<InvokeThunkDelegate>(context);
            IntPtr functionPtr = Marshal.GetFunctionPointerForDelegate(methodDelegate);

            // Keep a reference to the delegate to prevent it from being garbage collected
#if FLAX_EDITOR
            if (methodHolder.method.IsCollectible)
                cachedDelegatesCollectible[functionPtr] = methodDelegate;
            else
#endif
            {
                cachedDelegates[functionPtr] = methodDelegate;
            }
            return functionPtr;
#endif
        }

        [UnmanagedCallersOnly]
        internal static void FieldSetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr)
        {
            object fieldOwner = fieldOwnerHandle.IsAllocated ? fieldOwnerHandle.Target : null;
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            object value = MarshalToManaged(valuePtr, field.field.FieldType);
            field.field.SetValue(fieldOwner, value);
        }

        [UnmanagedCallersOnly]
        internal static int FieldGetOffset(ManagedHandle fieldHandle)
        {
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            return field.fieldOffset;
        }

        [UnmanagedCallersOnly]
        internal static void FieldGetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr)
        {
            object fieldOwner = fieldOwnerHandle.Target;
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            field.toNativeMarshaller(field.field, field.fieldOffset, fieldOwner, valuePtr, out int fieldSize);
        }

        [UnmanagedCallersOnly]
        internal static void FieldGetValueReference(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, int fieldOffset, IntPtr valuePtr)
        {
            object fieldOwner = fieldOwnerHandle.Target;
            IntPtr fieldRef;
#if USE_AOT || DOTNET_HOST_MONO
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            fieldRef = IntPtr.Zero;
            Debug.LogError("Not supported FieldGetValueReference");
#else
            if (fieldOwner.GetType().IsValueType)
            {
                fieldRef = FieldHelper.GetValueTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
            }
            else
            {
                fieldRef = FieldHelper.GetReferenceTypeFieldReference<object, IntPtr>(fieldOffset, ref fieldOwner);
            }
#endif
            Unsafe.Write<IntPtr>(valuePtr.ToPointer(), fieldRef);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr FieldGetValueBoxed(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle)
        {
            FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
            object fieldOwner = field.field.IsStatic ? null : fieldOwnerHandle.Target;
            object fieldValue = field.field.GetValue(fieldOwner);
            return Invoker.MarshalReturnValueGeneric(field.field.FieldType, fieldValue);
        }

        [UnmanagedCallersOnly]
        internal static void WriteArrayReference(ManagedHandle arrayHandle, IntPtr valueHandle, int index)
        {
            ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
            managedArray.ToSpan<IntPtr>()[index] = valueHandle;
        }

        [UnmanagedCallersOnly]
        internal static void WriteArrayReferences(ManagedHandle arrayHandle, IntPtr spanPtr, int spanLength)
        {
            ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
            var unmanagedSpan = new Span<IntPtr>(spanPtr.ToPointer(), spanLength);
            unmanagedSpan.CopyTo(managedArray.ToSpan<IntPtr>());
        }

        [UnmanagedCallersOnly]
        internal static void GetAssemblyName(ManagedHandle assemblyHandle, IntPtr* assemblyName, IntPtr* assemblyFullName)
        {
            Assembly assembly = Unsafe.As<Assembly>(assemblyHandle.Target);
            *assemblyName = NativeAllocStringAnsi(assembly.GetName().Name);
            *assemblyFullName = NativeAllocStringAnsi(assembly.FullName);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle LoadAssemblyImage(IntPtr assemblyPathPtr)
        {
            if (!firstAssemblyLoaded)
            {
                // This assembly was already loaded when initializing the host context
                firstAssemblyLoaded = true;

                Assembly flaxEngineAssembly = AssemblyLoadContext.Default.Assemblies.First(x => x.GetName().Name == "FlaxEngine.CSharp");
                return GetAssemblyHandle(flaxEngineAssembly);
            }
            try
            {
                string assemblyPath = Marshal.PtrToStringUni(assemblyPathPtr);

                Assembly assembly;
#if FLAX_EDITOR
                // Load assembly from loaded bytes to prevent file locking in Editor
                var assemblyBytes = File.ReadAllBytes(assemblyPath);
                using MemoryStream stream = new MemoryStream(assemblyBytes);
                var pdbPath = Path.ChangeExtension(assemblyPath, "pdb");
                if (File.Exists(pdbPath))
                {
                    // Load including debug symbols
                    using FileStream pdbStream = new FileStream(Path.ChangeExtension(assemblyPath, "pdb"), FileMode.Open);
                    assembly = scriptingAssemblyLoadContext.LoadFromStream(stream, pdbStream);
                }
                else
                {
                    assembly = scriptingAssemblyLoadContext.LoadFromStream(stream);
                }
#else
                // Load assembly from file
                assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
#endif
                if (assembly == null)
                    return new ManagedHandle();
                NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver);

                // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822
                AssemblyLocations.Add(assembly.FullName, assemblyPath);

                return GetAssemblyHandle(assembly);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
            return new ManagedHandle();
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetAssemblyByName(IntPtr namePtr)
        {
            string name = Marshal.PtrToStringAnsi(namePtr);
            Assembly assembly = Utils.GetAssemblies().FirstOrDefault(x => x.GetName().Name == name);
            if (assembly == null)
                return new ManagedHandle();
            return GetAssemblyHandle(assembly);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetRuntimeInformation()
        {
            return NativeAllocStringAnsi(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
        }

        [UnmanagedCallersOnly]
        internal static void CloseAssembly(ManagedHandle assemblyHandle)
        {
            Assembly assembly = Unsafe.As<Assembly>(assemblyHandle.Target);
            assemblyHandle.Free();
            assemblyHandles.Remove(assembly);

            AssemblyLocations.Remove(assembly.FullName);

            // Unload native library handles associated for this assembly
            string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly);
            if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary))
            {
                NativeLibrary.Free(nativeLibrary);
                loadedNativeLibraries.Remove(nativeLibraryName);
            }
            if (nativeLibraryName != null)
                libraryPaths.Remove(nativeLibraryName);
        }

        [UnmanagedCallersOnly]
        internal static void CreateScriptingAssemblyLoadContext()
        {
#if FLAX_EDITOR
            if (scriptingAssemblyLoadContext != null)
            {
                // Wait for previous ALC to finish unloading, track it without holding strong references to it
                GCHandle weakRef = GCHandle.Alloc(scriptingAssemblyLoadContext, GCHandleType.WeakTrackResurrection);
                scriptingAssemblyLoadContext = null;
#if false
                // In case the ALC doesn't unload properly: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#debug-unloading-issues
                while (true)
#else
                for (int attempts = 5; attempts > 0; attempts--)
#endif
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();

                    if (!IsHandleAlive(weakRef))
                        break;
                    System.Threading.Thread.Sleep(1);
                }
                if (IsHandleAlive(weakRef))
                    Debug.Logger.LogHandler.LogWrite(LogType.Warning, "Scripting AssemblyLoadContext was not unloaded.");
                weakRef.Free();

                static bool IsHandleAlive(GCHandle weakRef)
                {
                    // Checking the target in scope somehow holds a reference to it...?
                    return weakRef.Target != null;
                }
            }

            scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: true);
            scriptingAssemblyLoadContext.Resolving += OnScriptingAssemblyLoadContextResolving;
#else
            scriptingAssemblyLoadContext = new AssemblyLoadContext("Flax", isCollectible: false);
#endif
            DelegateHelpers.InitMethods();
        }

        [UnmanagedCallersOnly]
        internal static void UnloadScriptingAssemblyLoadContext()
        {
#if FLAX_EDITOR
            // Any of the windows might still process events while GC is blocking, the events
            // might go through the interop layer and require DelegateHelpers to invoke internal methods.
            // This seems to happen quite often with OnLostFocus event from debugger stealing the focus
            // after hitting a breakpoint in the middle of scripting ALC being reloaded.
            GC.Collect();
            GC.WaitForPendingFinalizers();

            // Clear all caches which might hold references to assemblies in collectible ALC
            cachedDelegatesCollectible.Clear();
            foreach (var pair in managedTypesCollectible)
                pair.Value.handle.Free();
            managedTypesCollectible.Clear();
            foreach (var handle in methodHandlesCollectible)
                handle.Free();
            methodHandlesCollectible.Clear();
            foreach (var handle in fieldHandleCacheCollectible)
                handle.Free();
            fieldHandleCacheCollectible.Clear();
            foreach (var handle in propertyHandleCacheCollectible)
                handle.Free();
            propertyHandleCacheCollectible.Clear();

            foreach (var key in assemblyHandles.Keys.Where(x => x.IsCollectible))
                assemblyHandles.Remove(key);
            foreach (var key in assemblyOwnedNativeLibraries.Keys.Where(x => x.IsCollectible))
                assemblyOwnedNativeLibraries.Remove(key);

            _typeSizeCache.Clear();

            foreach (var pair in classAttributesCacheCollectible)
                pair.Value.Free();
            classAttributesCacheCollectible.Clear();

            ArrayFactory.marshalledTypes.Clear();
            ArrayFactory.arrayTypes.Clear();
            ArrayFactory.createArrayDelegates.Clear();

            FlaxEngine.Json.JsonSerializer.ResetCache();

            // Ensure both pools are empty
            ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true);
            ManagedHandle.ManagedHandlePool.TryCollectWeakHandles(true);

            GC.Collect();
            GC.WaitForPendingFinalizers();

            // Release these as late as possible in case native code tries to call managed side during cleanup
            DelegateHelpers.Release();

            {
                // HACK: Workaround for TypeDescriptor holding references to collectible types (https://github.com/dotnet/runtime/issues/30656)

                Type typeDescriptionProviderType = typeof(System.ComponentModel.TypeDescriptionProvider);
                MethodInfo clearCacheMethod = typeDescriptionProviderType?.Assembly.GetType("System.ComponentModel.ReflectionCachesUpdateHandler")?.GetMethod("ClearCache");
                if (clearCacheMethod != null)
                    clearCacheMethod.Invoke(null, new object[] { null });

                Type TypeDescriptorType = typeof(System.ComponentModel.TypeDescriptor);
                object s_providerTable = TypeDescriptorType?.GetField("s_providerTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);

                // Added in .NET runtime 8.0.10, used as the main locking object
                object s_commonSyncObject = TypeDescriptorType?.GetField("s_commonSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
                if (s_commonSyncObject == null)
                    s_commonSyncObject = s_providerTable;

                // Removed in .NET runtime 8.0.7
                object s_internalSyncObject = TypeDescriptorType?.GetField("s_internalSyncObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
                object s_defaultProviders = TypeDescriptorType?.GetField("s_defaultProviders", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
                if (s_internalSyncObject != null && s_defaultProviders != null)
                {
                    lock (s_internalSyncObject)
                        InvokeClear(s_defaultProviders);
                }

                // Replaces s_defaultProviders in 8.0.7
                object s_defaultProviderInitialized = TypeDescriptorType?.GetField("s_defaultProviderInitialized", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
                if (s_commonSyncObject != null && s_defaultProviderInitialized != null)
                {
                    lock (s_commonSyncObject)
                        InvokeClear(s_defaultProviderInitialized);
                }

                object s_providerTypeTable = TypeDescriptorType?.GetField("s_providerTypeTable", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)?.GetValue(null);
                if (s_providerTable != null && s_providerTypeTable != null)
                {
                    lock (s_commonSyncObject)
                        InvokeClear(s_providerTypeTable);
                    InvokeClear(s_providerTable);
                }

                static void InvokeClear(object instance)
                {
                    Type type = instance.GetType();
                    Assertions.Assert.IsTrue(type.Name == "ConcurrentDictionary`2" || type.Name == "Hashtable" || type.Name == "WeakHashtable");
                    type.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(x => x.Name == "Clear")?.Invoke(instance, Array.Empty<object>());
                }
            }

            // Unload the ALC
            scriptingAssemblyLoadContext.Unload();
            scriptingAssemblyLoadContext.Resolving -= OnScriptingAssemblyLoadContextResolving;

            GC.Collect();
            GC.WaitForPendingFinalizers();
#endif
        }

        [UnmanagedCallersOnly]
        internal static int NativeSizeOf(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            Type nativeType = GetInternalType(type) ?? type;
            int size;
            if (nativeType.IsClass)
                size = sizeof(IntPtr);
            else
                size = GetTypeSize(nativeType);
            return size;
        }

        [UnmanagedCallersOnly]
        internal static byte TypeIsSubclassOf(ManagedHandle typeHandle, ManagedHandle otherTypeHandle, byte checkInterfaces)
        {
            if (typeHandle == otherTypeHandle)
                return 1;

            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            Type otherType = Unsafe.As<TypeHolder>(otherTypeHandle.Target);

            if (type == otherType)
                return 1;

            if (checkInterfaces != 0 && otherType.IsInterface)
            {
                if (type.GetInterfaces().Contains(otherType))
                    return 1;
            }

            return type.IsSubclassOf(otherType) ? (byte)1 : (byte)0;
        }

        [UnmanagedCallersOnly]
        internal static byte TypeIsAssignableFrom(ManagedHandle typeHandle, ManagedHandle otherTypeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            Type otherType = Unsafe.As<TypeHolder>(otherTypeHandle.Target);
            return (byte)(type.IsAssignableFrom(otherType) ? 1 : 0);
        }

        [UnmanagedCallersOnly]
        internal static byte TypeIsValueType(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            return (byte)(type.IsValueType ? 1 : 0);
        }

        [UnmanagedCallersOnly]
        internal static byte TypeIsEnum(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            return (byte)(type.IsEnum ? 1 : 0);
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetClassParent(ManagedHandle typeHandle)
        {
            TypeHolder typeHolder = Unsafe.As<TypeHolder>(typeHandle.Target);
            TypeHolder baseTypeHolder = GetTypeHolder(typeHolder.type.BaseType);
            return baseTypeHolder.managedClassPointer;
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetElementClass(ManagedHandle typeHandle)
        {
            TypeHolder typeHolder = Unsafe.As<TypeHolder>(typeHandle.Target);
            TypeHolder elementTypeHolder = GetTypeHolder(typeHolder.type.GetElementType());
            return elementTypeHolder.managedClassPointer;
        }

        [UnmanagedCallersOnly]
        internal static UInt64 GetMethodParameterIsOut(ManagedHandle methodHandle)
        {
            MethodHolder methodHolder = Unsafe.As<MethodHolder>(methodHandle.Target);
            var parameters = methodHolder.method.GetParameters();
            UInt64 result = 0;
            for (int i = 0; i < parameters.Length; i++)
            {
                if (parameters[i].IsOut)
                    result |= 1ul << i;
            }
            return result;
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetNullReferenceException()
        {
            var exception = new NullReferenceException();
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetException(IntPtr msgPtr)
        {
            string msg = Marshal.PtrToStringAnsi(msgPtr);
            var exception = new Exception(msg);
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetNotSupportedException()
        {
            var exception = new NotSupportedException();
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetArgumentNullException()
        {
            var exception = new ArgumentNullException();
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetArgumentException()
        {
            var exception = new ArgumentException();
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle GetArgumentOutOfRangeException()
        {
            var exception = new ArgumentOutOfRangeException();
            return ManagedHandle.Alloc(exception, GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle NewGCHandle(ManagedHandle valueHandle, byte pinned)
        {
            return ManagedHandle.Alloc(valueHandle.Target, pinned != 0 ? GCHandleType.Pinned : GCHandleType.Normal);
        }

        [UnmanagedCallersOnly]
        internal static ManagedHandle NewGCHandleWeak(ManagedHandle valueHandle, byte trackResurrection)
        {
            return ManagedHandle.Alloc(valueHandle.Target, trackResurrection != 0 ? GCHandleType.WeakTrackResurrection : GCHandleType.Weak);
        }

        [UnmanagedCallersOnly]
        internal static void FreeGCHandle(ManagedHandle valueHandle)
        {
            valueHandle.Free();
        }

        [UnmanagedCallersOnly]
        internal static void GCCollect(int generation, int mode, bool blocking, bool compacting)
        {
            GC.Collect(generation, (GCCollectionMode)mode, blocking, compacting);
        }

        [UnmanagedCallersOnly]
        internal static int GCMaxGeneration()
        {
            return GC.MaxGeneration;
        }

        [UnmanagedCallersOnly]
        internal static void GCMemoryInfo(long* totalCommitted, long* heapSize)
        {
            GCMemoryInfo gcMemoryInfo = GC.GetGCMemoryInfo();
            *totalCommitted = gcMemoryInfo.TotalCommittedBytes;
            *heapSize = gcMemoryInfo.HeapSizeBytes;
        }

        [UnmanagedCallersOnly]
        internal static void GCWaitForPendingFinalizers()
        {
            GC.WaitForPendingFinalizers();
        }

        [UnmanagedCallersOnly]
        internal static IntPtr GetTypeClass(ManagedHandle typeHandle)
        {
            TypeHolder typeHolder = Unsafe.As<TypeHolder>(typeHandle.Target);
            if (typeHolder.type.IsByRef)
            {
                // Drop reference type (&) to get actual value type
                return GetTypeHolder(typeHolder.type.GetElementType()).managedClassPointer;
            }
            return typeHolder.managedClassPointer;
        }

        [UnmanagedCallersOnly]
        internal static bool GetTypeIsPointer(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            return type.IsPointer;
        }

        [UnmanagedCallersOnly]
        internal static bool GetTypeIsReference(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            return !type.IsValueType; // Maybe also type.IsByRef?
        }

        [UnmanagedCallersOnly]
        internal static uint GetTypeMTypesEnum(ManagedHandle typeHandle)
        {
            Type type = Unsafe.As<TypeHolder>(typeHandle.Target);
            if (type.IsByRef)
                type = type.GetElementType(); // Drop reference type (&) to get actual value type
            MTypes monoType;
            switch (type)
            {
            case Type _ when type == typeof(void):
                monoType = MTypes.Void;
                break;
            case Type _ when type == typeof(bool):
                monoType = MTypes.Boolean;
                break;
            case Type _ when type == typeof(sbyte):
            case Type _ when type == typeof(short):
                monoType = MTypes.I2;
                break;
            case Type _ when type == typeof(byte):
            case Type _ when type == typeof(ushort):
                monoType = MTypes.U2;
                break;
            case Type _ when type == typeof(int):
                monoType = MTypes.I4;
                break;
            case Type _ when type == typeof(uint):
                monoType = MTypes.U4;
                break;
            case Type _ when type == typeof(long):
                monoType = MTypes.I8;
                break;
            case Type _ when type == typeof(ulong):
                monoType = MTypes.U8;
                break;
            case Type _ when type == typeof(float):
                monoType = MTypes.R4;
                break;
            case Type _ when type == typeof(double):
                monoType = MTypes.R8;
                break;
            case Type _ when type == typeof(string):
                monoType = MTypes.String;
                break;
            case Type _ when type == typeof(IntPtr):
                monoType = MTypes.Ptr;
                break;
            case Type _ when type.IsPointer:
                monoType = MTypes.Ptr;
                break;
            case Type _ when type.IsEnum:
                monoType = MTypes.Enum;
                break;
            case Type _ when type.IsArray:
            case Type _ when type == typeof(ManagedArray):
                monoType = MTypes.Array;
                break;
            case Type _ when type.IsValueType && !type.IsEnum && !type.IsPrimitive:
                monoType = MTypes.ValueType;
                break;
            case Type _ when type.IsGenericType:
                monoType = MTypes.GenericInst;
                break;
            case Type _ when type.IsClass:
                monoType = MTypes.Object;
                break;
            default: throw new NativeInteropException($"Unsupported type '{type.FullName}'");
            }
            return (uint)monoType;
        }
    }
}

#endif
