using System;
using GlitchLog;
using System.Reflection;
using GlitchyEngine.Events;
using GlitchyEngine.Math;
using GlitchyEngine.World;
using GlitchyEngine.Core;
using System.Collections;
using Box2D;
using GlitchyEngine.Scripting;
using GlitchyEngine.Serialization;
using GlitchyEngine.Editor;
using GlitchyEngine.World.Components;
using GlitchyEngine.Content;
using GlitchyEngine.Renderer;
using System.Diagnostics;
using System.IO;
using System.Linq;
using static GlitchyEngine.Scripting.ScriptGlue;

using static GlitchyEngine.Renderer.Text.FontRenderer;

namespace GlitchyEngine.Scripting;

using internal GlitchyEngine.Scripting;
using internal GlitchyEngine.Content;

// TODO: Move to logger?
class MessageOrigin
{
	private String _fileName ~ delete:append _;
	private int _lineNumber;

	public StringView FileName => _fileName;
	public int LineNumber => _lineNumber;

	[AllowAppend]
	public this(StringView fileName, int lineNumber)
	{
		String file = append String(fileName);

		_fileName = file;
		_lineNumber = lineNumber;
	}
}

[AttributeUsage(.Method)]
struct RegisterCallAttribute : Attribute
{
	public bool EngineResultAsBool { get; set mut; }
	public bool IsExtension { get; set mut; }
}

struct TypeTranslationTemplate
{
	public String StartCode;
	public String EndCode;
	public String PrettyCSharpParamType;

	public this(String startCode, String endCode, String prettyCSharpParamType)
	{
		StartCode = startCode;
		EndCode = endCode;
		PrettyCSharpParamType = prettyCSharpParamType;
	}
}

[AttributeUsage(.Parameter)]
struct GlueParamAttribute : Attribute
{
	public String WrapperType;

	public this(String wrapperType)
	{
		WrapperType = wrapperType;
	}
}

[AttributeUsage(.Struct)]
struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply
{
	private static String GetTypeInfo(Type type, RegisterCallAttribute? callAttribute = null)
	{
		String value = new .();
		type.ToString(value);

		if (type.IsEnum)
		{
			// Pass the underlying integer type for enums.
			value.AppendF($" : {type.UnderlyingType}");
		}
		else if (!type.HasCustomAttribute<CReprAttribute>() && !type.IsPrimitive && !type.IsPointer && !(type is RefType))
		{
			// For CDecl Functions beef ecpects non CRepr struct to be passed as readonly ref.
			// Thus we add in to the type name. However primitive types are passed by value!
			value.Insert(0, "in ");
		}

		// EngineResult will usually just be converted to exceptions -> script method returns void
		// But sometimes we want to be able to also return a boolean value.
		if (type == typeof(EngineResult) && callAttribute.HasValue)
		{
			if (callAttribute.Value.EngineResultAsBool)
			{
				value.Append(" as bool");
			}
			else
			{
				value.Append(" as void");
			}
		}

		return value;
	}
	
	private static void GenerateJsonInfo(MethodInfo method, RegisterCallAttribute registerCallAttribute, String outString)
	{
		String parameters = new String();
		
		StringView str = method.Name;

		for (int i < method.ParamCount)
		{
			if (i != 0)
				parameters.Append(", ");

			parameters.AppendF($"""

							{{
								"name": "{method.GetParamName(i)}",
								"type": "{GetTypeInfo(method.GetParamType(i))}"
							}}
				""");
		}

		outString.AppendF($"""

				{{
					"name": "{method.Name}",
					"return_type": "{GetTypeInfo(method.ReturnType, registerCallAttribute)}",
					"parameters": [{parameters}
					]
				}},
			""");
	}

	[Comptime]
	public void ApplyToType(Type self)
    {
		String jsonOutput = new String("[");

        for (MethodInfo methodInfo in typeof(ScriptGlue).GetMethods(.Static | .NonPublic))
		{
			if (methodInfo.GetCustomAttribute<RegisterCallAttribute>() case .Ok(let attribute))
			{
				String parameters = new String();

				for (int i < methodInfo.ParamCount)
				{
					if (i != 0)
						parameters.Append(", ");

					parameters.AppendF($"{methodInfo.GetParamType(i)}");
				}

				String line = scope $"public function [CallingConvention(.Cdecl)] {methodInfo.ReturnType}({parameters}) {methodInfo.Name};\n";

				Compiler.EmitTypeBody(self, line);

				GenerateJsonInfo(methodInfo, attribute, jsonOutput);
			}
		}

		if (jsonOutput.EndsWith(","))
		{
			jsonOutput.RemoveFromEnd(1);
		}

		jsonOutput.Append(']');

		Directory.CreateDirectory("../generated");

		if (File.WriteAllText("../generated/ScriptGlue.json", jsonOutput) case .Err)
		{
			Runtime.FatalError("Failed to write file");
		}
    }
}

[EngineFunctionsGenerator]
struct EngineFunctions
{
	static Self _functions;
	
	[RegisterMethod]
	public static void FillEngineFunctions()
	{
		
		OnRegisterNativeCalls.Invoke();

		ScriptGlue.[Friend]_setEngineFunctions(&_functions);
	}
}

/* Adding this attribute to a method will log method entry and returned Result<T> errors */
[AttributeUsage(.Method | .Constructor)]
public struct RegisterMethodAttribute : Attribute, IOnMethodInit
{
	public bool IsExtension = false;

    [Comptime]
    public void OnMethodInit(MethodInfo method, Self* prev)
    {
		String functionContent = new .();

		int i = 0;

		BindingFlags flags = .Static;

		if (IsExtension)
		{
			flags |= .DeclaredOnly;
		}

        for (var methodInfo in typeof(ScriptGlue).GetMethods(flags))
		{
			if (methodInfo.GetCustomAttribute<RegisterCallAttribute>() not case .Ok(let attribute))
				continue;

			if (IsExtension && attribute.IsExtension)
			{
				functionContent.AppendF($"EngineFunctions.[Friend]_functions.{methodInfo.Name} = => ScriptGlue.{methodInfo.Name};\n");
				i++;
			}
			else if (!IsExtension && !attribute.IsExtension)
			{
				functionContent.AppendF($"_functions.{methodInfo.Name} = => ScriptGlue.[Friend]{methodInfo.Name};\n");
				i++;
			}
		}

		//functionContent.Insert(0, scope $"EngineFunctions functions = .();\n");

		Compiler.EmitMethodEntry(method, functionContent);
    }
}

static class ScriptGlue
{
	public static Event<delegate void()> OnRegisterNativeCalls ~ _.Dispose();

	private function void SetEngineFunctions(EngineFunctions* engineFunctions);
	private static SetEngineFunctions _setEngineFunctions;

	private static String _lastExceptionMessage = new .() ~ delete _;

	public static void Init()
	{
		if (_setEngineFunctions == null)
		{
			CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "SetEngineFunctions", out _setEngineFunctions);
		}

		RegisterCalls();

		RegisterManagedComponents();
	}

	public static void RegisterManagedComponents()
	{
		Debug.Profiler.ProfileFunction!();

		RegisterComponent<TransformComponent>();
		RegisterComponent<Rigidbody2DComponent>();
		RegisterComponent<CameraComponent>();
		RegisterComponent<SpriteRendererComponent>();
		RegisterComponent<CircleRendererComponent>();
		RegisterComponent<TextRendererComponent>();
		RegisterComponent<MeshComponent>();
		RegisterComponent<MeshRendererComponent>();
	}

	private static void RegisterCalls()
	{
		EngineFunctions.FillEngineFunctions();
	}
	
	private static void RegisterComponent<T>() where T : struct, new
	{
		String fullComponentTypeName = scope String();
		typeof(T).GetFullName(fullComponentTypeName);

		CoreClrHelper.RegisterComponent(fullComponentTypeName..EnsureNullTerminator(),
			addComponent: (entityId) =>
			{
				if (GetEntitySafe(entityId) case .Ok(Entity entity))
				{
					entity.AddComponent<T>();
					return .Ok;
				}
				
				return .EntityNotFound;
			},
			hasComponent: (entityId) =>
			{
				if (GetEntitySafe(entityId) case .Ok(Entity entity))
				{
					return entity.HasComponent<T>() ? .Ok : .False;
				}
				
				return .EntityNotFound;
			},
			removeComponent: (entityId) =>
			{
				if (GetEntitySafe(entityId) case .Ok(Entity entity))
				{
					entity.RemoveComponent<T>();
					return .Ok;
				}
				
				return .EntityNotFound;
			});
	}

	/// Gets the entity with the given id. Throws a mono exception, if the entity doesn't exist.
	static Result<Entity> GetEntitySafe(UUID entityId)
	{
		if (ScriptEngine.Context.GetEntityByID(entityId) case .Ok(let entity))
		{
			return entity;
		}

		return .Err;
	}

	static mixin GetEntityOrReturnError(UUID entityId)
	{
		if (GetEntitySafe(entityId) not case .Ok(let entity))
		{
			return EngineResult.EntityNotFound;
		}

		entity
	}

	/// Gets the component of the specified type that is attached to the given entity. Or null, if the entity doesn't exist or doesn't have the specified component.
	static Result<T*, EngineResult> GetComponentSafe<T>(UUID entityId) where T: struct, new
	{
		Result<Entity> foundEntity = ScriptEngine.Context.GetEntityByID(entityId);

		if (foundEntity case .Ok(let entity))
		{
			if (entity.TryGetComponent<T>(let component))
			{
				return component;
			}
			else
			{
				return .Err(.EntityDoesntHaveComponent);
			}
		}
		else
		{
			return .Err(.EntityNotFound);
		}
	}

	static mixin GetComponentOrReturn<T>(UUID entityId) where T: struct, new
	{
		Result<T*, EngineResult> result = GetComponentSafe<T>(entityId);
		if (result case .Err(EngineResult error))
		{
			return error;
		}

		result.Value
	}

	/// Gets the component of the specified type that is attached to the given entity. Or null, if the entity doesn't exist or doesn't have the specified component.
	static bool TryGetComponentSafe<T>(UUID entityId, out T* component) where T: struct, new
	{
		Result<Entity> foundEntity = ScriptEngine.Context.GetEntityByID(entityId);

		if (foundEntity case .Ok(let entity))
		{
			if (entity.TryGetComponent<T>(out component))
			{
				return true;
			}
			else
			{
				Log.ClientLogger.Error($"Entity {entityId} has no {nameof(T)}.");
			}
		}
		else
		{
			Log.ClientLogger.Error($"No entity with ID {entityId} found.");
		}

		component = null;
		return false;
	}
	
	/// Gets the entity. Returns true, if the entity was found.
	static bool TryGetEntitySafe(UUID entityId, out Entity entity)
	{
		entity = default;

		Result<Entity> foundEntity = ScriptEngine.Context.GetEntityByID(entityId);

		if (foundEntity case .Ok(out entity))
		{
			return true;
		}

		Log.ClientLogger.Error($"No entity with ID {entityId} found.");
		return false;
	}

	public enum EngineResult : int32
	{
		case Ok = 0; // Success, for boolean return value means True
		case False = 1; // Success, for boolean return value means False
		case Error = -1;
		case NotImplemented = -2;
		case ArgumentError = -3;

		// Entity Errors:
		case EntityNotFound = -4; // The entity doesn't exist or was deleted.
		case EntityDoesntHaveComponent = -5; // The entity has no component of type {typeof(T)} or it was deleted.
		case AssetNotFound = -6; // No asset exists for AssetHandle \"{assetId}\".

		case CustomError = 1 << 31;

		public static EngineResult CustomError(int32 payload)
		{
			return CustomError | payload;
		}
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static StringView GetLastExceptionMessage()
	{
		return _lastExceptionMessage;
	}

#region Log

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Log_LogMessage(LogLevel logLevel, StringView messagePtr, StringView fileNamePtr, int lineNumber)
	{
		String escapedMessage = new:ScopedAlloc! String(messagePtr);

		// Escape the message, because the editor logger does a string-format
		escapedMessage.Replace("{", "{{");
		escapedMessage.Replace("}", "}}");

		if (!fileNamePtr.IsEmpty)
		{
			String fileName = new:ScopedAlloc! String(fileNamePtr);

			MessageOrigin messageOrigin = scope MessageOrigin(fileName, lineNumber);

			Log.ClientLogger.Log((LogLevel)logLevel, escapedMessage, messageOrigin);
		}
		else
		{
			Log.ClientLogger.Log((LogLevel)logLevel, escapedMessage);
		}
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Log_LogException(UUID entityId, StringView fullExceptionClassName, StringView exceptionMessage, StringView stackTrace)
	{
		ScriptException exception = new ScriptException(entityId, fullExceptionClassName, exceptionMessage, stackTrace);
		ScriptEngine.LogScriptException(exception, entityId);
	}

#endregion

#region Input

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsKeyPressed(Key key) => Input.IsKeyPressed(key);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsKeyReleased(Key key) => Input.IsKeyReleased(key);
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsKeyToggled(Key key) => Input.IsKeyToggled(key);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsKeyPressing(Key key) => Input.IsKeyPressing(key);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsKeyReleasing(Key key) => Input.IsKeyReleasing(key);

	
	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsMouseButtonPressed(MouseButton mouseButton) => Input.IsMouseButtonPressed(mouseButton);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsMouseButtonReleased(MouseButton mouseButton) => Input.IsMouseButtonReleased(mouseButton);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsMouseButtonPressing(MouseButton mouseButton) => Input.IsMouseButtonPressing(mouseButton);

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Input_IsMouseButtonReleasing(MouseButton mouseButton) => Input.IsMouseButtonReleasing(mouseButton);

#endregion Input

#region Scene/Entity stuff

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Entity_Create(StringView entityName, out UUID entityId)
	{
		Entity entity = ScriptEngine.Context.CreateEntity(entityName);

		entityId = entity.UUID;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Entity_Destroy(UUID entityId)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		ScriptEngine.Context.DestroyEntityDeferred(entity);
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Entity_CreateInstance(UUID entityId, out UUID newEntityId)
	{
		newEntityId = ?;
		Entity entity = GetEntityOrReturnError!(entityId);
		Entity newEntity = ScriptEngine.Context.CreateInstance(entity);

		newEntityId = newEntity.UUID;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Entity_FindEntityWithName(StringView entityName, out UUID outUuid)
	{
		outUuid = UUID(0);

		Result<Entity> entityResult = ScriptEngine.Context.GetEntityByName(entityName);

		if (entityResult case .Ok(let entity))
			outUuid = entity.UUID;
	}

	// TODO: Do we need this?
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Entity_GetScriptInstance(UUID entityId, out void* instance)
	{
		instance = ?;
 		return .NotImplemented;
		//instance = ScriptEngine.GetManagedInstance(entityId);
	}

	[RegisterCall(EngineResultAsBool = true), CallingConvention(.Cdecl)]
	static EngineResult Entity_SetScript(UUID entityId, StringView fullScriptTypeName)
	{
		Entity entity = GetEntityOrReturnError!(entityId);

		ScriptComponent* scriptComponent = null;

		if (!entity.HasComponent<ScriptComponent>())
		{
			scriptComponent = entity.AddComponent<ScriptComponent>();
		}
		else
		{
			scriptComponent = entity.GetComponent<ScriptComponent>();
		}

		if (scriptComponent.Instance != null)
			ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, false);

		scriptComponent.Instance = null;

		scriptComponent.ScriptClassName = fullScriptTypeName;
		
		// Initializes the created instance
		// TODO: this returns false, if no script with ScriptClassName exists, we have to handle this case correctly I think.
		if (!ScriptEngine.InitializeInstance(entity, scriptComponent))
		{
			return .ArgumentError;
		}

		//return scriptComponent.Instance.MonoInstance;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Entity_RemoveScript(UUID entityId)
	{
		ScriptComponent* scriptComponent = GetComponentOrReturn!<ScriptComponent>(entityId);

		ScriptEngine.Context.DestroyScriptDeferred(scriptComponent.Instance, true);

		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
    static StringView Entity_GetName(UUID entityId)
	{
		Result<Entity> foundEntity = ScriptEngine.Context.GetEntityByID(entityId);

		if (foundEntity case .Ok(let entity))
		{
			return entity.Name;
		}

		return null;
	}
    
	[RegisterCall, CallingConvention(.Cdecl)]
    static EngineResult Entity_SetName(UUID entityId, StringView name)
	{
		GetEntityOrReturnError!(entityId).Name = name;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
    static void Entity_GetEditorFlags(UUID entityId, out EditorFlags editorFlags)
	{
		editorFlags = .Default;
#if GE_EDITOR
		if (TryGetEntitySafe(entityId, let entity))
			editorFlags = entity.EditorFlags;
#endif
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
    static void Entity_SetEditorFlags(UUID entityId, EditorFlags editorFlags)
	{
#if GE_EDITOR
		if (TryGetEntitySafe(entityId, let entity))
			entity.EditorFlags = editorFlags;
#endif
	}

#endregion

#region TransformComponen

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetParent(UUID entityId, out UUID parentId)
	{
		parentId = .Zero;
		Entity entity = GetEntityOrReturnError!(entityId);

		parentId = entity.Parent?.UUID ?? .Zero;

		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetParent(UUID entityId, UUID newParentId)
	{
		Entity entity = GetEntityOrReturnError!(entityId);

		Entity? newParent = null;
		if (newParentId != .Zero)
		{
			GetEntityOrReturnError!(newParentId);
		}

		entity.Parent = newParent;

		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetTranslation(UUID entityId, out float3 translation)
	{
		translation = ?;
		Entity entity = GetEntityOrReturnError!(entityId);
		translation = entity.Transform.Position;

		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetTranslation(UUID entityId, in float3 translation)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		entity.Transform.Position = translation;

		// if necessary reposition Rigidbody2D
		if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		{
			rigidbody2D.SetPosition(translation.XY);
		}
		// TODO: we need to handle repositioning of colliders that are children of the entity with rigidbody...
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetWorldTranslation(UUID entityId, out float3 translationWorld)
	{
		translationWorld = ?;
		Entity entity = GetEntityOrReturnError!(entityId);

		TransformComponent* transform = entity.Transform;

		translationWorld = transform.WorldTransform.Translation; //(float4(entity.Transform.Position, 1.0f) * entity.Transform.WorldTransform).XYZ;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetWorldTranslation(UUID entityId, in float3 translation)
	{
		Log.ClientLogger.Warning("Transform_SetWorldTranslation is not implemented!");
		return .NotImplemented;

		// Entity entity = GetEntitySafe(entityId);

		// entity.Transform.Position = translation;

		// // if necessary reposition Rigidbody2D
		// if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		// {
		// 	rigidbody2D.SetPosition(translation.XY);
		// }
		// TODO: we need to handle repositioning of colliders that are children of the entity with rigidbody...
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_TransformPointToWorld(UUID entityId, float3 point, out float3 pointWorld)
	{
		pointWorld = ?;
		Entity entity = GetEntityOrReturnError!(entityId);

		float3 localPoint = entity.Transform.Position;

		pointWorld = (float4(localPoint, 1.0f) * entity.Transform.WorldTransform).XYZ;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetRotation(UUID entityId, out Quaternion rotation)
	{
		rotation = ?;
		Entity entity = GetEntityOrReturnError!(entityId);

		rotation = entity.Transform.Rotation;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetRotation(UUID entityId, in Quaternion rotation)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		
		entity.Transform.Rotation = rotation;

		if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		{
			rigidbody2D.SetAngle(entity.Transform.RotationEuler.Z);
		}
		// TODO: When rotating around the X- or Y-axis we need to deform and reposition the colliders accordingly...
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetRotationEuler(UUID entityId, out float3 rotationEuler)
	{
		rotationEuler = ?;
		Entity entity = GetEntityOrReturnError!(entityId);
		
		rotationEuler = entity.Transform.RotationEuler;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetRotationEuler(UUID entityId, in float3 rotationEuler)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		
		entity.Transform.RotationEuler = rotationEuler;

		if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		{
			rigidbody2D.SetAngle(rotationEuler.Z);
		}
		return .Ok;
	}

	[Packed]
	public struct AxisAngle
	{
		public float3 Axis;
		public float Angle;

		public this((float3 Axis, float Angle) axisAngle)
		{
			Axis = axisAngle.Axis;
			Angle = axisAngle.Angle;
		}
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetRotationAxisAngle(UUID entityId, out AxisAngle rotationAxisAngle)
	{
		rotationAxisAngle = ?;
		Entity entity = GetEntityOrReturnError!(entityId);
		
		rotationAxisAngle = AxisAngle(entity.Transform.RotationAxisAngle);
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetRotationAxisAngle(UUID entityId, AxisAngle rotationAxisAngle)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		
		entity.Transform.RotationAxisAngle = (rotationAxisAngle.Axis, rotationAxisAngle.Angle);

		if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		{
			rigidbody2D.SetAngle(entity.Transform.RotationEuler.Z);
		}
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_GetScale(UUID entityId, out float3 scale)
	{
		scale = ?;
		Entity entity = GetEntityOrReturnError!(entityId);
		
		scale = entity.Transform.Scale;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Transform_SetScale(UUID entityId, float3 scale)
	{
		Entity entity = GetEntityOrReturnError!(entityId);
		
		entity.Transform.Scale = scale;

		if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
		{
			// TODO: we need to scale and reposition the colliders, this is a mess...
		}
		return .Ok;
	}

#endregion TransformComponent

#region Rigidbody2
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_ApplyForce(UUID entityId, in float2 force, in float2 point, bool wakeUp)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		Box2D.Body.ApplyForce(rigidBody.[Friend]RuntimeBody, force, point, wakeUp);
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_ApplyForceToCenter(UUID entityId, in float2 force, bool wakeUp)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		Box2D.Body.ApplyForceToCenter(rigidBody.[Friend]RuntimeBody, force, wakeUp);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetPosition(UUID entityId, in float2 position)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.SetPosition(position);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetPosition(UUID entityId, out float2 position)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		position = rigidBody.GetPosition();
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetRotation(UUID entityId, float rotation)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.SetAngle(rotation);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetRotation(UUID entityId, out float rotation)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rotation = rigidBody.GetAngle();
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetLinearVelocity(UUID entityId, out float2 velocity)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		velocity = rigidBody.GetLinearVelocity();
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetLinearVelocity(UUID entityId, in float2 velocity)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.SetLinearVelocity(velocity);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetAngularVelocity(UUID entityId, out float velocity)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		velocity = rigidBody.GetAngularVelocity();
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetAngularVelocity(UUID entityId, float velocity)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.SetAngularVelocity(velocity);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetBodyType(UUID entityId, out Rigidbody2DComponent.BodyType bodyType)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		bodyType = rigidBody.BodyType;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetBodyType(UUID entityId, Rigidbody2DComponent.BodyType bodyType)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.BodyType = bodyType;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_IsFixedRotation(UUID entityId, out bool isFixedRotation)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		isFixedRotation = rigidBody.FixedRotation;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetFixedRotation(UUID entityId, bool isFixedRotation)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.FixedRotation = isFixedRotation;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_GetGravityScale(UUID entityId, out float gravityScale)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		gravityScale = rigidBody.GravityScale;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Rigidbody2D_SetGravityScale(UUID entityId, float gravityScale)
	{
		Rigidbody2DComponent* rigidBody = GetComponentSafe<Rigidbody2DComponent>(entityId);
		rigidBody.GravityScale = gravityScale;
	}

#endregion Rigidbody2D

#region Camer

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetProjectionType(UUID entityId, out SceneCamera.ProjectionType projectionType)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		projectionType = camera.Camera.ProjectionType;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetProjectionType(UUID entityId, SceneCamera.ProjectionType projectionType)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.ProjectionType = projectionType;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetPerspectiveFovY(UUID entityId, out float fovY)
	{
	    CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		fovY = camera.Camera.PerspectiveFovY;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetPerspectiveFovY(UUID entityId, float fovY)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.PerspectiveFovY = fovY;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetPerspectiveNearPlane(UUID entityId, out float nearPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		nearPlane = camera.Camera.PerspectiveNearPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetPerspectiveNearPlane(UUID entityId, float nearPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.PerspectiveNearPlane = nearPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetPerspectiveFarPlane(UUID entityId, out float farPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		farPlane = camera.Camera.PerspectiveFarPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetPerspectiveFarPlane(UUID entityId, float farPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.PerspectiveFarPlane = farPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetOrthographicHeight(UUID entityId, out float height)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		height = camera.Camera.OrthographicHeight;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetOrthographicHeight(UUID entityId, float height)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.OrthographicHeight = height;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetOrthographicNearPlane(UUID entityId, float nearPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.OrthographicNearPlane = nearPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetOrthographicNearPlane(UUID entityId, out float nearPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		nearPlane = camera.Camera.OrthographicNearPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetOrthographicFarPlane(UUID entityId, float farPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.OrthographicFarPlane = farPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetOrthographicFarPlane(UUID entityId, out float farPlane)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		farPlane = camera.Camera.OrthographicFarPlane;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetAspectRatio(UUID entityId, float aspectRatio)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.AspectRatio = aspectRatio;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetAspectRatio(UUID entityId, out float aspectRatio)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		aspectRatio = camera.Camera.AspectRatio;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_SetFixedAspectRatio(UUID entityId, bool fixedAspectRatio)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		camera.Camera.FixedAspectRatio = fixedAspectRatio;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Camera_GetFixedAspectRatio(UUID entityId, out bool fixedAspectRatio)
	{
		CameraComponent* camera = GetComponentSafe<CameraComponent>(entityId);
		fixedAspectRatio = camera.Camera.FixedAspectRatio;
	}

#endregion

#region Physics2D

	// TODO: We need a wrapper class!

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Physics2D_GetGravity(out float2 gravity)
	{
		Scene scene = ScriptEngine.Context;

		gravity = scene.Physics2DSettings.Gravity;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Physics2D_SetGravity(in float2 gravity)
	{
		Scene scene = ScriptEngine.Context;

		scene.Physics2DSettings.Gravity = gravity;
	}

#endregion Physics2D

	// TODO: Do we even need the circle renderer?
#region CircleRenderer
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_GetColor(UUID entityId, out ColorRGBA color)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		color = circleRenderer.Color;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_SetColor(UUID entityId, ColorRGBA color)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		circleRenderer.Color = color;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_GetUvTransform(UUID entityId, out float4 uvTransform)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		uvTransform = circleRenderer.UvTransform;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_SetUvTransform(UUID entityId, float4 uvTransform)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		circleRenderer.UvTransform = uvTransform;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_GetInnerRadius(UUID entityId, out float innerRadius)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		innerRadius = circleRenderer.InnerRadius;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static void CircleRenderer_SetInnerRadius(UUID entityId, float innerRadius)
	{
		CircleRendererComponent* circleRenderer = GetComponentSafe<CircleRendererComponent>(entityId);
		circleRenderer.InnerRadius = innerRadius;
	}

#endregion

#region SpriteRenderer

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_GetColor(UUID entityId, out ColorRGBA color)
	{
		color = ?;
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);
		color = spriteRenderer.Color;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_SetColor(UUID entityId, ColorRGBA color)
	{
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);
		spriteRenderer.Color = color;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_GetUvTransform(UUID entityId, out float4 uvTransform)
	{
		uvTransform = ?;
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);
		uvTransform = spriteRenderer.UvTransform;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_SetUvTransform(UUID entityId, float4 uvTransform)
	{
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);
		spriteRenderer.UvTransform = uvTransform;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_GetMaterial(UUID entityId, out AssetHandle assetId)
	{
		assetId = ?;
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);

		Material material = GetAssetOrReturn!<Material>((AssetHandle)spriteRenderer.Material);

		if (!material.IsRuntimeInstance)
		{
			using (material = new Material(material, true))
			{
				material.Identifier = scope $"(Instance) {material.Identifier}";
				//Content.ManageAsset(material);
				spriteRenderer.Material = material.Handle;
			}
		}

		assetId = spriteRenderer.Material;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult SpriteRenderer_SetMaterial(UUID entityId, AssetHandle assetId)
	{
		SpriteRendererComponent* spriteRenderer = GetComponentOrReturn!<SpriteRendererComponent>(entityId);
		spriteRenderer.Material = assetId;
		return .Ok;
	}


#endregion

#region TextRenderer

	[RegisterCall(EngineResultAsBool = true), CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_GetIsRichText(UUID entityId)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);
		return textComponent.IsRichText ? .Ok : .False;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_SetIsRichText(UUID entityId, bool isRichText)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);

		textComponent.IsRichText = isRichText;
		textComponent.NeedsRebuild = true;

		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_GetText(UUID entityId, out StringView text)
	{
		text = ?;
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);

		text = textComponent.Text;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_SetText(UUID entityId, StringView text)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);

		textComponent.Text = text;

		textComponent.NeedsRebuild = true;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_GetColor(UUID entityId, out ColorRGBA color)
	{
		color = ?;
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);

		color = textComponent.Color;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_SetColor(UUID entityId, ColorRGBA color)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);

		textComponent.Color = color;
		textComponent.NeedsRebuild = true;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_GetHorizontalAlignment(UUID entityId, [GlueParam("out HorizontalTextAlignment")] out HorizontalTextAlignment horizontalAlignment)
	{
		horizontalAlignment = ?;
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);
		horizontalAlignment = textComponent.HorizontalAlignment;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_SetHorizontalAlignment(UUID entityId, [GlueParam("HorizontalTextAlignment")] HorizontalTextAlignment horizontalAlignment)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);
		textComponent.HorizontalAlignment = horizontalAlignment;
		textComponent.NeedsRebuild = true;
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_GetFontSize(UUID entityId, out float fontSize)
	{
		fontSize = ?;
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);
		fontSize = textComponent.FontSize;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult TextRenderer_SetFontSize(UUID entityId, float fontSize)
	{
		TextRendererComponent* textComponent = GetComponentOrReturn!<TextRendererComponent>(entityId);
		textComponent.FontSize = fontSize;
		textComponent.NeedsRebuild = true;
		return .Ok;
	}

#endregion

#region Mesh
#endregion

#region MeshRenderer

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult MeshRenderer_GetMaterial(UUID entityId, out AssetHandle assetId)
	{
		assetId = ?;
		MeshRendererComponent* meshRenderer = GetComponentOrReturn!<MeshRendererComponent>(entityId);

		Material material = GetAssetOrReturn!<Material>((AssetHandle)meshRenderer.Material);
		
		if (!material.IsRuntimeInstance)
		{
			using (material = new Material(material, true))
			{
				material.Identifier = scope $"(Instance) {material.Identifier}";
				//Content.ManageAsset(material);
				meshRenderer.Material = material.Handle;
			}
		}

		assetId = meshRenderer.Material;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult MeshRenderer_GetSharedMaterial(UUID entityId, out AssetHandle assetId)
	{
		assetId = ?;
		MeshRendererComponent* meshRenderer = GetComponentOrReturn!<MeshRendererComponent>(entityId);

		Material material = GetAssetOrReturn!<Material>((AssetHandle)meshRenderer.Material);

		if (material.IsRuntimeInstance)
		{
			assetId = material.Parent.Handle;
		}
		else
		{
			assetId = meshRenderer.Material;
		}
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult MeshRenderer_SetMaterial(UUID entityId, AssetHandle assetId)
	{
		MeshRendererComponent* meshRenderer = GetComponentOrReturn!<MeshRendererComponent>(entityId);
		meshRenderer.Material = assetId;
		return .Ok;
	}

#endregion

#region Math
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static float Math_ModfFloat(float x, out float integerPart)
	{
		return modf(x, out integerPart);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static float2 Math_ModfFloat2(float2 x, out float2 integerPart)
	{
		return modf(x, out integerPart);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static float3 Math_ModfFloat3(float3 x, out float3 integerPart)
	{
		return modf(x, out integerPart);
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static float4 Math_ModfFloat4(float4 x, out float4 integerPart)
	{
		return modf(x, out integerPart);
	}

#endregion

	[RegisterCall, CallingConvention(.Cdecl)]
	static void UUID_Create(out UUID id)
	{
		id = UUID.Create();
	}

#region Application
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Application_IsEditor()
	{
		return ScriptEngine.ApplicationInfo.IsEditor;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Application_IsPlayer()
	{
		return ScriptEngine.ApplicationInfo.IsPlayer;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Application_IsInEditMode()
	{
		return ScriptEngine.ApplicationInfo.IsInEditMode;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static bool Application_IsInPlayMode()
	{
		return ScriptEngine.ApplicationInfo.IsInPlayMode;
	}

#endregion

#region Serialization

	[RegisterCall, CallingConvention(.Cdecl)]
	static void Serialization_SerializeField(void* serializationContext, SerializationType type, StringView fieldName, void* valueObject, StringView fullTypeName)
	{
		Debug.Profiler.ProfileFunction!();

		SerializedObject context = Internal.UnsafeCastToObject(serializationContext) as SerializedObject;

		Log.EngineLogger.AssertDebug(context != null);

		context.AddField(fieldName, type, valueObject, fullTypeName);
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Serialization_CreateObject(void* currentContext, bool isStatic, StringView typeName, out void* newContext, out UUID newId)
	{
		Debug.Profiler.ProfileFunction!();

		SerializedObject context = Internal.UnsafeCastToObject(currentContext) as SerializedObject;

		Log.EngineLogger.AssertDebug(context != null);
		
		SerializedObject newObject = new SerializedObject(context.Serializer, isStatic, typeName);

		newContext = Internal.UnsafeCastToPtr(newObject);
		newId = newObject.Id;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Serialization_DeserializeField(void* internalContext, SerializationType expectedType, StringView fieldName, uint8* target, out SerializationType actualType)
	{
		SerializedObject context = Internal.UnsafeCastToObject(internalContext) as SerializedObject;

		Log.EngineLogger.AssertDebug(context != null);
		
		context.GetField(fieldName, expectedType, target, out actualType);
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Serialization_GetObject(void* internalContext, UUID id, out void* objectContext)
	{
		SerializedObject context = Internal.UnsafeCastToObject(internalContext) as SerializedObject;

		Log.EngineLogger.AssertDebug(context != null);
		
		objectContext = null;

		Log.EngineLogger.AssertDebug(context.Serializer.ContainsId(context.Id));

		if (!context.Serializer.TryGetSerializedObject(id, let foundObject))
			return;

		objectContext = Internal.UnsafeCastToPtr(foundObject);
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static void Serialization_GetObjectTypeName(void* internalContext, out StringView fullTypeName)
	{
		SerializedObject context = Internal.UnsafeCastToObject(internalContext) as SerializedObject;

		Log.EngineLogger.AssertDebug(context != null);

		fullTypeName = context.TypeName;
	}

#endregion

#region Asset

	/*static mixin GetAssetOrThrow(UUID assetId)
	{
		AssetHandle handle = .(assetId);
		Asset asset = Content.GetAsset(handle, blocking: true);

		if (asset == null)
		{
			ThrowInvalidOperationException(scope $"No asset exists for AssetHandle \"{assetId}\".");
		}

		asset
	}*/

	static void SetExceptionMessage(StringView message)
	{
		_lastExceptionMessage.Set(message);
	}

	static mixin GetAssetOrReturn<T>(UUID assetId) where T : Asset
	{
		AssetHandle handle = .(assetId);
		T asset = Content.GetAsset<T>(handle, blocking: true);

		if (asset == null)
		{
			SetExceptionMessage(scope $"Asset (Id: {assetId}) not found.");
			return EngineResult.AssetNotFound;
		}

		asset
	}

	static mixin GetAssetOrReturn<T>(AssetHandle assetHandle) where T : Asset
	{
		T asset = Content.GetAsset<T>(assetHandle, blocking: true);

		if (asset == null)
		{
			SetExceptionMessage(scope $"Asset (Handle: {assetHandle}) not found.");
			return EngineResult.AssetNotFound;
		}

		asset
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Asset_GetIdentifier(UUID assetId, out StringView identifier)
	{
		identifier = ?;
		Asset asset = GetAssetOrReturn!<Asset>(assetId);

		identifier = asset.Identifier;
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Asset_SetIdentifier(UUID assetId, StringView identifier)
	{
		return .NotImplemented;

		//AssetHandle handle = .(assetId);
		//Asset asset = Content.GetAsset(handle, blocking: true);

		//ThrowNotImplementedException("Asset.GetIdentifier is not implemented.");

		/*
		// TODO: I think it's not THAT easy.
		asset.Identifier = StringView(rawText);
		*/
	}

#endregion

#region Material
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Material_SetVariable(AssetHandle assetHandle, StringView variableName, ShaderVariableType elementType, int32 rows, int32 columns, int32 arrayLength, void* rawData, int32 dataLength)
	{
		Material material = GetAssetOrReturn!<Material>(assetHandle);

		if (material.[Friend]SetVariableRaw(variableName, elementType, rows, columns , arrayLength, Span<uint8>((uint8*)rawData, dataLength)) case .Err(let error))
		{
			switch (error)
			{
			case .VariableNotFound:
				SetExceptionMessage(scope $"Material \"{material.Identifier}\" has no variable \"{variableName}\".");
				return .ArgumentError;
			case .ElementTypeMismatch:
				SetExceptionMessage(scope $"Variable \"{variableName}\" has incompatible element type.");
				return .ArgumentError;
			case .MatrixDimensionMismatch:
				SetExceptionMessage(scope $"Variable \"{variableName}\" has incompatible matrix dimension type.");
				return .ArgumentError;
			case .ProvidedBufferTooShort:
				SetExceptionMessage(scope $"The provided data buffer for variable \"{variableName}\" is not large enough.");
			}
			return .Error;
		}
		
		return .Ok;
	}
	
	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Material_ResetVariable(AssetHandle assetHandle, StringView variableName)
	{
		Material material = GetAssetOrReturn!<Material>(assetHandle);

		material.ResetVariable(variableName);
		
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Material_SetTexture(AssetHandle materialHandle, StringView variableName, AssetHandle textureHandle)
	{
		Material material = GetAssetOrReturn!<Material>(materialHandle);

		material.SetTexture(variableName, textureHandle);
		
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Material_GetTexture(AssetHandle materialHandle, StringView variableName, out AssetHandle textureHandle)
	{
		textureHandle = ?;
		Material material = GetAssetOrReturn!<Material>(materialHandle);

		var v = material.GetTexture(variableName, ?);

		if (v case .Err(let err))
		{
			return .Error;
		}

		textureHandle = v.Value;
		
		return .Ok;
	}

	[RegisterCall, CallingConvention(.Cdecl)]
	static EngineResult Material_ResetTexture(AssetHandle materialHandle, StringView variableName)
	{
		Material material = GetAssetOrReturn!<Material>(materialHandle);

		material.ResetTexture(variableName);

		return .Ok;
	}

#endregion
}