using ImGui;
using GlitchyEngine.World;
using System;
using GlitchyEngine.Math;
using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine;
using GlitchyEngine.Content;
using GlitchyEngine.Scripting;
using GlitchyEngine.Core;
using Mono;
using GlitchyEngine.World.Components;

namespace GlitchyEditor.EditWindows
{
	using internal GlitchyEngine.World.TransformComponent;

	class ComponentEditWindow
	{
		private static uint32 TableId;

		public static void ShowComponents(Entity entity, Type componentType = null)
		{
			float cellPaddingY = ImGui.GetTextLineHeight() / 3.0f;

			ImGui.PushStyleVar(.CellPadding, ImGui.Vec2(ImGui.GetStyle().CellPadding.x, cellPaddingY));

			TableId = ImGui.GetID("properties");

			if (componentType == null && ImGui.BeginTableEx("properties", TableId, 2, .SizingStretchSame | .BordersInner | .Resizable))
			{
				ShowNameComponentEditor(entity);

				ImGui.EndTable();
			}

			ShowComponentEditor<TransformComponent>(componentType, "Transform", entity, => ShowTransformComponentEditor);
			ShowComponentEditor<CameraComponent>(componentType, "Camera", entity, => ShowCameraComponentEditor, => ShowComponentContextMenu<CameraComponent>);
			
			ShowComponentEditor<SpriteRendererComponent>(componentType, "Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriteRendererComponent>);
			ShowComponentEditor<CircleRendererComponent>(componentType, "Circle Renderer", entity, => ShowCircleRendererComponentEditor, => ShowComponentContextMenu<CircleRendererComponent>);
			ShowComponentEditor<TextRendererComponent>(componentType, "Text Renderer", entity, => ShowTextRendererComponentEditor, => ShowComponentContextMenu<TextRendererComponent>);

			ShowComponentEditor<MeshRendererComponent>(componentType, "Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu<MeshRendererComponent>);
			ShowComponentEditor<LightComponent>(componentType, "Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu<LightComponent>);
			ShowComponentEditor<MeshComponent>(componentType, "Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu<MeshComponent>);

			ShowComponentEditor<Rigidbody2DComponent>(componentType, "Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu<Rigidbody2DComponent>);
			ShowComponentEditor<BoxCollider2DComponent>(componentType, "Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu<BoxCollider2DComponent>);
			ShowComponentEditor<CircleCollider2DComponent>(componentType, "Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu<CircleCollider2DComponent>);
			ShowComponentEditor<PolygonCollider2DComponent>(componentType, "Polygon collider 2D", entity, => ShowPolygonCollider2DComponentEditor, => ShowComponentContextMenu<PolygonCollider2DComponent>);

			ScriptComponent:
			{
				String scriptComponentName = "Script";

				if (entity.TryGetComponent<ScriptComponent>(let scriptComponent) && scriptComponent.Instance != null)
				{
					scriptComponentName = scope:ScriptComponent $"Script ({scriptComponent.ScriptClassName})";
				}

				ShowComponentEditor<ScriptComponent>(componentType, scriptComponentName, entity, => ShowScriptComponentEditor, => ShowComponentContextMenu<ScriptComponent>);
			}

			ImGui.PopStyleVar();

			if (componentType == null)
			{
				ShowAddComponentButton(entity);
			}
		}

		public static void DrawSceneGUI(Entity entity)
		{
			DrawComponenSceneGUI<PolygonCollider2DComponent>(entity, => ShowPolygonColliderSceneGUI);
		}

		static void ShowPolygonColliderSceneGUI(Entity entity, PolygonCollider2DComponent* collider)
		{
			if (_editVerticesPolygonCollider2D)
			{
				for (int i < collider.VertexCount)
				{
					Matrix localToWorld = entity.Transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0);
					Matrix transform = Matrix.Translation(float3(collider.Vertices[i], 0)) * localToWorld;
	
					if (Handles.ShowGizmo(ref transform, .TRANSLATE_X | .TRANSLATE_Y, true, id: (int32)i))
					{
						transform = localToWorld.Invert() * transform;
	
						collider.Vertices[i] = transform.Translation.XY;
					}
				}
			}
		}

		private static void DrawComponenSceneGUI<TComponent>(Entity entity, function void(Entity, TComponent*) showSceneGUI) where TComponent: struct, new
		{
			if (!entity.HasComponent<TComponent>())
				return;

			TComponent* component = entity.GetComponent<TComponent>();

			showSceneGUI(entity, component);
		}

		private static void ShowComponentContextMenu<TComponent>(Entity entity, TComponent* component) where TComponent: struct, new
		{
			if (ImGui.Selectable("Remove Component"))
				entity.RemoveComponent<TComponent>();
		}

		private static void ShowOnlyComponent<TComponent>(UUID entityId, function void(Entity, TComponent*) showComponentEditor) where TComponent: struct, new
		{
			if (Entity entity = Editor.Instance.CurrentScene.GetEntityByID(entityId))
			{
				if (entity.TryGetComponent<TComponent>(let component))
				{
					if (ImGui.BeginPropertyTable("properties", TableId))
					{
						if (entity.TryGetComponent<TComponent>(let actualComponent))
							showComponentEditor(entity, actualComponent);
		
						ImGui.EndTable();
					}
				}
				else
				{
					ImGui.TextWrapped($"ERROR!\n\nEntity {entity.Name} doesn't have a component of type {nameof(TComponent)}.");
				}
			}
			else
			{
				ImGui.TextWrapped($"ERROR!\n\nEntity {entityId} doesn't exist.");
			}
		}

		static PropertiesWindow window = null;

		private static void ShowComponentEditor<TComponent>(Type soloType, String header, Entity entity, function void(Entity, TComponent*) showComponentEditor, function void(Entity, TComponent*) showComponentContextMenu = null) where TComponent: struct, new
		{
			if ((soloType != null && soloType != typeof(TComponent)) || !entity.HasComponent<TComponent>())
				return;

			TComponent* component = entity.GetComponent<TComponent>();

			bool nodeOpen = (soloType != null) || ImGui.CollapsingHeader(header.CStr(), .DefaultOpen | .AllowOverlap | .Framed | .SpanFullWidth);
			
			ImGui.PushID(header.CStr());
			defer { ImGui.PopID(); }
			
			if (ImGui.IsItemActive() && ImGui.IsMouseDragging(.Left) && ImGui.GetIO().KeyAlt && window == null)
			{
				window = new PropertiesWindow(Editor.Instance, .Component(entity.UUID, typeof(TComponent)));
				window.WindowTitle = header;
			}
			else if (!ImGui.GetIO().KeyAlt)
			{
				window = null;
			}

			if (window != null)
			{
				ImGui.SetWindowFocus(window.WindowTitleWithId.ToScopeCStr!());

				ImGui.Vec2 position = ImGui.GetMousePos();
				position.y -= ImGui.GetTextLineHeight() / 2.0f;
				position.x -= 100.0f;

				ImGui.SetWindowPos(window.WindowTitleWithId.ToScopeCStr!(), position);
			}

			if (showComponentContextMenu != null && ImGui.IsItemClicked(.Right))
			{
				ImGui.OpenPopup("component_popup");
			}

			float positionX = ImGui.GetWindowContentRegionMax().x;
			
			float2 buttonSize = ImGui.GetTextLineHeight().XX;
			
			ImGui.PushStyleColor(.Button, .(0, 0, 0, 0));

			if (showComponentContextMenu != null)
			{
				positionX -= buttonSize.X + ImGui.GetStyle().FramePadding.x;
				ImGui.SameLine(positionX);
				positionX -= ImGui.GetStyle().FramePadding.x;

				if (ImGui.ImageButton("save", EditorIcons.Instance.Icon_ContextMenu, (.)buttonSize))
				{
					ImGui.OpenPopup("component_popup");
				}

				if (ImGui.BeginPopup("component_popup"))
				{
					showComponentContextMenu(entity, component);
					
					ImGui.EndPopup();
				}
			}

			if (ScriptEngine.ApplicationInfo.IsInPlayMode)
			{
				positionX -= buttonSize.X + ImGui.GetStyle().FramePadding.x;
				ImGui.SameLine(positionX);

				if (ImGui.ImageButton("save", EditorIcons.Instance.Icon_Save, (.)buttonSize))
				{
					if (Entity editorEntity = Editor.Instance.EditorScene.GetEntityByID(entity.UUID))
					{
						Scene.CopyComponent<TComponent>(entity, editorEntity);

						if (typeof(TComponent) == typeof(ScriptComponent))
						{
							// TODO: Copy script data into editor
							// TODO: Somehow get the scriptserializer used to serialize the editor scene
							//scriptSerializer.SerializeScriptInstance(((ScriptComponent*)component).Instance);
						}
					}
					else
					{
						ImGui.OpenPopup("###CopyNewEntityToEditor");
					}
				}

				ImGui.AttachTooltip("Save the component to edit mode");
			}

			ImGui.PopStyleColor();
			
			if (ImGui.BeginPopupModal("Create new entity?###CopyNewEntityToEditor"))
			{
				//ImGui.Text("This entity only exists in play mode. Do you want to copy only this component, or all components?");
				ImGui.Text("This entity only exists in play mode.\nSaving will create an entity that only contains this component in edit mode.");
				
				//if (ImGui.Button("Copy only this component"))
				if (ImGui.Button("Ok"))
				{
					Entity editorEntity = Editor.Instance.EditorScene.CreateEntity(entity.Name, entity.UUID);

					Scene.CopyComponent<TComponent>(entity, editorEntity);

					if (typeof(TComponent) == typeof(ScriptComponent))
					{
						// TODO: Copy script data into editor
						// TODO: Somehow get the scriptserializer used to serialize the editor scene
						//scriptSerializer.SerializeScriptInstance(((ScriptComponent*)component).Instance);
					}

					ImGui.CloseCurrentPopup();
				}

				ImGui.SameLine();

				// TODO!
				/*if (ImGui.Button("Copy all components"))
				{
					ImGui.CloseCurrentPopup();
				}

				ImGui.SameLine();
				*/

				if (ImGui.Button("Cancel"))
				{
					ImGui.CloseCurrentPopup();
				}

				ImGui.EndPopup();
			}

			if (nodeOpen && ImGui.BeginPropertyTable("properties", TableId))
			{
				if (entity.TryGetComponent<TComponent>(let actualComponent))
					showComponentEditor(entity, actualComponent);

				ImGui.EndTable();
			}
		}

		private static void ShowNameComponentEditor(Entity entity)
		{
			if (!entity.HasComponent<NameComponent>())
				return;

			char8[256] nameBuffer = default;

			NameComponent* component = entity.GetComponent<NameComponent>();

			StringView name = null;

			if(component != null)
			{
				name = component.Name;
			}
			else
			{
				name = scope:: $"Entity {entity.Handle.[Friend]Index}";
			}

			// Copy name to buffer
			Internal.MemCpy(&nameBuffer, name.Ptr, Math.Min(nameBuffer.Count, name.Length));

			ImGui.PushItemWidth(-1);

			ImGui.PropertyTableStartNewProperty("Name");

			ImGui.PushItemWidth(-1);

			if(ImGui.InputText("##Name", &nameBuffer, nameBuffer.Count, .EnterReturnsTrue))
			{
				if(component == null)
				{
					component = entity.AddComponent<NameComponent>();
				}

				component.Name = StringView(&nameBuffer);
			}
		}

		private static void ShowTransformComponentEditor(Entity entity, TransformComponent* transform)
		{
			float textWidth = ImGui.CalcTextSize("Position".CStr()).x;
			textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Rotation".CStr()).x);
			textWidth = Math.Max(textWidth, ImGui.CalcTextSize("Scale".CStr()).x);

			textWidth += ImGui.GetStyle().FramePadding.x * 3.0f;

			float3 position = transform.Position;

			ImGui.PropertyTableStartNewProperty("Position");
			if (ImGui.Float3Editor("##Position", ref position, resetValues: .Zero, dragSpeed: 0.1f))
			{
				transform.Position = position;

				// if necessary reposition Rigidbody2D
				if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
				{
					rigidbody2D.SetPosition(position.XY);
				}
			}

			float3 rotationEuler = MathHelper.ToDegrees(transform.EditorRotationEuler);

			bool3 componentEditable = true;

			if (entity.HasComponent<Rigidbody2DComponent>())
			{
				// Disable X and Y rotation for 2D rigid bodies
				componentEditable.XY = false;
				rotationEuler.XY = 0;
			}
			
			ImGui.PropertyTableStartNewProperty("Rotation");
			if (ImGui.Float3Editor("##Rotation", ref rotationEuler, resetValues: .Zero, dragSpeed: 0.1f, componentEnabled: componentEditable, format: .("0.#####°",)))
			{
				transform.EditorRotationEuler = MathHelper.ToRadians(rotationEuler);
				
				// if necessary reposition Rigidbody2D
				if (entity.TryGetComponent<Rigidbody2DComponent>(let rigidbody2D))
				{
					rigidbody2D.SetAngle(rotationEuler.Z);
				}
			}
			
			float3 scale = transform.Scale;

			ImGui.PropertyTableStartNewProperty("Scale");
			if (ImGui.Float3Editor("##Scale", ref scale, resetValues: .One, dragSpeed: 0.1f))
				transform.Scale = scale;
		}
		
		static String[] strings = new String[]("Orthographic", "Perspective", "Perspective (Infinite)") ~ delete _;

		private static void ShowCameraComponentEditor(Entity entity, CameraComponent* cameraComponent)
		{
			ImGui.PropertyTableStartNewProperty("Is Primary");
			ImGui.Checkbox("##Is_Primary", &cameraComponent.Primary);

			var camera = ref cameraComponent.Camera;

			String typeName = strings[camera.ProjectionType.Underlying];
			
			ImGui.PropertyTableStartNewProperty("Projection");
			if (ImGui.BeginCombo("##Projection", typeName.CStr()))
			{
				for (int i = 0; i < 3; i++)
				{
					bool isSelected = (typeName == strings[i]);

					if (ImGui.Selectable(strings[i], isSelected))
					{
						camera.ProjectionType = (.)i;
					}

					if (isSelected)
						ImGui.SetItemDefaultFocus();
				}

				ImGui.EndCombo();
			}

			if (camera.ProjectionType == .Perspective)
			{
				ImGui.PropertyTableStartNewProperty("Fov Y");
				float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY);
				if (ImGui.DragFloat("##Fov Y", &fovY, 0.1f, format: "%.3g°"))
					camera.PerspectiveFovY = MathHelper.ToRadians(fovY);
				
				ImGui.PropertyTableStartNewProperty("Near");
				float near = camera.PerspectiveNearPlane;
				if (ImGui.DragFloat("##Near", &near, 0.1f, format: "%.3g"))
					camera.PerspectiveNearPlane = near;
				
				ImGui.PropertyTableStartNewProperty("Far");
				float far = camera.PerspectiveFarPlane;
				if (ImGui.DragFloat("##Far", &far, 0.1f, format: "%.3g"))
					camera.PerspectiveFarPlane = far;
			}
			else if (camera.ProjectionType == .InfinitePerspective)
			{
				ImGui.PropertyTableStartNewProperty("Vertical FOV");
				float fovY = MathHelper.ToDegrees(camera.PerspectiveFovY);
				if (ImGui.DragFloat("##Vertical FOV", &fovY, 0.1f, format: "%.3g°"))
					camera.PerspectiveFovY = MathHelper.ToRadians(fovY);
				
				ImGui.PropertyTableStartNewProperty("Near");
				float near = camera.PerspectiveNearPlane;
				if (ImGui.DragFloat("##Near", &near, 0.1f, format: "%.3g"))
					camera.PerspectiveNearPlane = near;
			}
			else if (camera.ProjectionType == .Orthographic)
			{
				ImGui.PropertyTableStartNewProperty("Size");
				float size = camera.OrthographicHeight;
				if (ImGui.DragFloat("##Size", &size, 0.1f, format: "%.3g"))
					camera.OrthographicHeight = size;
				
				ImGui.PropertyTableStartNewProperty("Near");
				float near = camera.OrthographicNearPlane;
				if (ImGui.DragFloat("##Near", &near, 0.1f, format: "%.3g"))
					camera.OrthographicNearPlane = near;
				
				ImGui.PropertyTableStartNewProperty("Far");
				float far = camera.OrthographicFarPlane;
				if (ImGui.DragFloat("##Far", &far, 0.1f, format: "%.3g"))
					camera.OrthographicFarPlane = far;
			}
			
			ImGui.PropertyTableStartNewProperty("Fixed Aspect Ratio");
			bool fixedAspectRatio = camera.FixedAspectRatio;
			if (ImGui.Checkbox("##Fixed Aspect Ratio", &fixedAspectRatio))
				camera.FixedAspectRatio = fixedAspectRatio;

			if (fixedAspectRatio)
			{
				ImGui.PropertyTableStartNewProperty("Aspect Ratio");
				float aspect = camera.AspectRatio;
				if (ImGui.DragFloat("##Aspect Ratio", &aspect, 0.1f, format: "%.3g"))
					camera.AspectRatio = aspect;
			}
		}

		private static void ShowSpriteRendererComponentEditor(Entity entity, SpriteRendererComponent* spriteRendererComponent)
		{
			ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(spriteRendererComponent.Color);
			ImGui.PropertyTableStartNewProperty("Color");
			if (ImGui.ColorEdit4("##Color", ref spriteColor))
				spriteRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor);
			
			ImGui.PropertyTableStartNewProperty("Texture");
			ShowAssetDropTarget(ref spriteRendererComponent.Sprite);
			/*ImGui.Button("...");

			if (ImGui.BeginDragDropTarget())
			{
				ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);

				if (payload != null)
				{
					Log.EngineLogger.Warning("");

					StringView path = .((char8*)payload.Data, (int)payload.DataSize);

					spriteRendererComponent.Sprite = Content.LoadAsset(path);
				}

				ImGui.EndDragDropTarget();
			}*/

			ImGui.PropertyTableStartNewProperty("UV Transform");
			ImGui.Float4Editor("##UV Transform", ref spriteRendererComponent.UvTransform, resetValues: float4(0, 0, 1, 1));

			ImGui.PropertyTableStartNewProperty("Material");
			
			ShowAssetDropTarget<Material>(ref spriteRendererComponent.Material);
		}
		
		public static bool ShowAssetDropTarget(ref AssetHandle target)
		{
			bool changed = false;

			Asset currentAsset = Content.GetAsset(target);

			StringView identifier = currentAsset?.Identifier ?? (target.IsValid ? "<Missing Asset>" : "None");

			if (ImGui.Button(identifier.ToScopeCStr!()))
			{
				// TODO: Show asset selector
			}

			if (ImGui.BeginDragDropTarget())
			{
				ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);

				if (payload != null)
				{
					Log.EngineLogger.Warning("");

					// We shouldn't pass the path/identifier. We should carry ID and Type!
					StringView path = .((char8*)payload.Data, (int)payload.DataSize);

					// TODO: Somehow validate the type, please!
					target = (AssetHandle)Content.LoadAsset(path);
					changed = true;
				}

				ImGui.EndDragDropTarget();
			}

			return changed;
		}

		public static bool ShowAssetDropTarget<T>(ref AssetHandle<T> target) where T : Asset
		{
			bool changed = false;

			T currentAsset = target.Get();

			StringView identifier = (target.IsValid ? "<Missing Asset>" : "None");

			if (currentAsset != null)
				identifier = currentAsset.Identifier;
			
			if (ImGui.Button(identifier.ToScopeCStr!()))
			{
				// TODO: Show asset selector
			}

			if (ImGui.BeginDragDropTarget())
			{
				ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);

				if (payload != null)
				{
					Log.EngineLogger.Warning("");

					StringView path = .((char8*)payload.Data, (int)payload.DataSize);

					// TODO: Somehow validate the type, please!
					target = (AssetHandle<T>)Content.LoadAsset(path);
					changed = true;
				}

				ImGui.EndDragDropTarget();
			}

			return changed;
		}

		private static void ShowCircleRendererComponentEditor(Entity entity, CircleRendererComponent* circleRendererComponent)
		{
			ColorRGBA spriteColor = ColorRGBA.LinearToSRGB(circleRendererComponent.Color);
			ImGui.PropertyTableStartNewProperty("Color");
			if (ImGui.ColorEdit4("##Color", ref spriteColor))
				circleRendererComponent.Color = ColorRGBA.SRgbToLinear(spriteColor);
			
			ImGui.PropertyTableStartNewProperty("Texture");
			ImGui.Button("...");

			if (ImGui.BeginDragDropTarget())
			{
				ImGui.Payload* payload = ImGui.AcceptDragDropPayload(.ContentBrowserItem);

				if (payload != null)
				{
					Log.EngineLogger.Warning("");

					StringView path = .((char8*)payload.Data, (int)payload.DataSize);

					circleRendererComponent.Sprite = Content.LoadAsset(path);
				}

				ImGui.EndDragDropTarget();
			}

			ImGui.PropertyTableStartNewProperty("UV Transform");
			ImGui.Float4Editor("##UV Transform", ref circleRendererComponent.UvTransform, resetValues: float4(0, 0, 1, 1));
			
			ImGui.PropertyTableStartNewProperty("Inner Radius");
			ImGui.DragFloat("##Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f, format: "%.3g");
		}
		
		static int InputTextCallback(ImGui.InputTextCallbackData* data)
		{
			if (data.EventFlag == .CallbackResize)
			{
				String str = (String)Internal.UnsafeCastToObject(data.UserData);

				Log.EngineLogger.AssertDebug(str.Ptr == data.Buf);

				str.PadRight(data.BufTextLen * 2, '\0');
				data.Buf = str.Ptr;

				return 0;
			}

			return 0;
		}

		private static void ShowTextRendererComponentEditor(Entity entity, TextRendererComponent* textRendererComponent)
		{
			ImGui.PropertyTableStartNewProperty("Rich text");

			ImGui.AttachTooltip("If checked, the text will be interpreted as rich text.");

			bool isRichText = textRendererComponent.IsRichText;

			if (ImGui.Checkbox("##rich_text", &isRichText))
			{
				textRendererComponent.IsRichText = isRichText;
				textRendererComponent.NeedsRebuild = true;
			}

			ImGui.PropertyTableStartNewProperty("Text");

			String text = textRendererComponent.[Friend]_text;

			if (text == null)
			{
				text = textRendererComponent.[Friend]_text = new String();
			}

			text?.EnsureNullTerminator();

			if (ImGui.InputTextMultiline("##text", text.CStr(), (uint64)(text.Length + 1), .Zero, .CallbackResize, => InputTextCallback, Internal.UnsafeCastToPtr(text)))
			{
				// To deleting text, ImGui simply puts a null char after the remaining text.
				// Search for null char and set the length of the string accordingly.
				int length = text.IndexOf('\0');

				if (length >= 0)
				{
					text.Length = length;
				}
				
				textRendererComponent.NeedsRebuild = true;
			}

			ImGui.PropertyTableStartNewProperty("Font Size");

			if (ImGui.DragFloat("##fontSize", &textRendererComponent.FontSize, format: "%.3g"))
			{
				textRendererComponent.NeedsRebuild = true;
			}

			ImGui.PropertyTableStartNewProperty("Color");

			if (ImGui.ColorEdit4("##color", ref textRendererComponent.Color))
			{
				textRendererComponent.NeedsRebuild = true;
			}

			ImGui.PropertyTableStartNewProperty("Horizontal Alignment");

			if (ImGui.EnumCombo("##horAlign", ref textRendererComponent.HorizontalAlignment))
			{
				textRendererComponent.NeedsRebuild = true;
			}
		}

		private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent)
		{
			ImGui.PropertyTableStartNewProperty("Material");
			
			ShowAssetDropTarget<Material>(ref meshRendererComponent.Material);
		}
		
		private static void ShowRigidBody2DComponentEditor(Entity entity, Rigidbody2DComponent* rigidBodyComponent)
		{
			const String[?] bodyTypeStrings = .("Static", "Dynamic", "Kinematic");
			String bodyTypeName = bodyTypeStrings[rigidBodyComponent.BodyType.Underlying];
			
			ImGui.PropertyTableStartNewProperty("Type");
			if (ImGui.BeginCombo("##Type", bodyTypeName.CStr()))
			{
				for (int i = 0; i < 3; i++)
				{
					bool isSelected = (bodyTypeName == bodyTypeStrings[i]);

					if (ImGui.Selectable(bodyTypeStrings[i], isSelected))
					{
						rigidBodyComponent.BodyType = (.)i;
					}

					if (isSelected)
						ImGui.SetItemDefaultFocus();
				}

				ImGui.EndCombo();
			}

			bool isFixedRotation = rigidBodyComponent.FixedRotation;
			ImGui.PropertyTableStartNewProperty("Fixed Rotation");
			if (ImGui.Checkbox("##Fixed Rotation", &isFixedRotation))
			{
				rigidBodyComponent.FixedRotation = isFixedRotation;
			}

			float gravityScale = rigidBodyComponent.GravityScale;
			ImGui.PropertyTableStartNewProperty("Gravity Scale");
			if (ImGui.DragFloat("##Gravity Scale", &gravityScale, format: "%.3g"))
			{
				rigidBodyComponent.GravityScale = gravityScale;
			}
		}

		private static void ShowBoxCollider2DComponentEditor(Entity entity, BoxCollider2DComponent* boxCollider)
		{
			float2 offset = boxCollider.Offset;
			ImGui.PropertyTableStartNewProperty("Offset");
			if (ImGui.Float2Editor("##Offset", ref offset, .Zero, 0.1f))
				boxCollider.Offset = offset;

			float2 size = boxCollider.Size;
			ImGui.PropertyTableStartNewProperty("Size");
			if (ImGui.Float2Editor("##Size", ref size, .Zero, 0.1f, float2(0.01f, 0.01f), float.PositiveInfinity.XX))
				boxCollider.Size = size;
			
			float density = boxCollider.Density;
			ImGui.PropertyTableStartNewProperty("Density");
			if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f, format: "%.3g"))
				boxCollider.Density = density;
			
			float friction = boxCollider.Friction;
			ImGui.PropertyTableStartNewProperty("Friction");
			if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f, format: "%.3g"))
				boxCollider.Friction = friction;

			float restitution = boxCollider.Restitution;
			ImGui.PropertyTableStartNewProperty("Restitution");
			if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f, format: "%.3g"))
				boxCollider.Restitution = restitution;

			float restitutionThreshold = boxCollider.RestitutionThreshold;
			ImGui.PropertyTableStartNewProperty("Restitution Threshold");
			if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, format: "%.3g"))
				boxCollider.RestitutionThreshold = restitutionThreshold;
		}

		private static void ShowCircleCollider2DComponentEditor(Entity entity, CircleCollider2DComponent* circleCollider)
		{
			float2 offset = circleCollider.Offset;
			ImGui.PropertyTableStartNewProperty("Offset");
			if (ImGui.Float2Editor("##Offset", ref offset, .Zero, 0.1f))
				circleCollider.Offset = offset;

			float radius = circleCollider.Radius;
			ImGui.PropertyTableStartNewProperty("Radius");
			if (ImGui.DragFloat("##Radius", &radius, 0.0f, 0.1f, format: "%.3g"))
				circleCollider.Radius = radius;
			
			float density = circleCollider.Density;
			ImGui.PropertyTableStartNewProperty("Density");
			if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f, format: "%.3g"))
				circleCollider.Density = density;
			
			float friction = circleCollider.Friction;
			ImGui.PropertyTableStartNewProperty("Friction");
			if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f, format: "%.3g"))
				circleCollider.Friction = friction;

			float restitution = circleCollider.Restitution;
			ImGui.PropertyTableStartNewProperty("Restitution");
			if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f, format: "%.3g"))
				circleCollider.Restitution = restitution;

			float restitutionThreshold = circleCollider.RestitutionThreshold;
			ImGui.PropertyTableStartNewProperty("Restitution Threshold");
			if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, format: "%.3g"))
				circleCollider.RestitutionThreshold = restitutionThreshold;
		}

		/// If true handles for editing the vertices of the PolygonCollider2DComponent will be visible 
		private static bool _editVerticesPolygonCollider2D = false;

		/// Shows a collapsing header in a separate table row.
		private static bool CollapsingHeader(StringView header)
		{
			ImGui.TableNextRow();
			ImGui.TableSetColumnIndex(0);

			return ImGui.CollapsingHeader("Vertices", .SpanAllColumns);
		}

		private static void ShowPolygonCollider2DComponentEditor(Entity entity, PolygonCollider2DComponent* polygonCollider)
		{
			float2 offset = polygonCollider.Offset;
			ImGui.PropertyTableStartNewProperty("Offset");
			if (ImGui.Float2Editor("Offset", ref offset, .Zero, 0.1f))
				polygonCollider.Offset = offset;

			ImGui.PropertyTableStartNewRow();

			bool isOpen = ImGui.TreeNodeEx("Vertices", .AllowOverlap | .SpanAllColumns);

			ImGui.TableSetColumnIndex(1);

			var addButtonWidth = ImGui.CalcTextSize("+").x + 2 * ImGui.GetStyle().FramePadding.x;
			var removeButtonWidth = ImGui.CalcTextSize("-").x + 2 * ImGui.GetStyle().FramePadding.x;
			
			ImGui.SameLine(ImGui.GetContentRegionAvail().x - addButtonWidth - ImGui.GetStyle().FramePadding.x - removeButtonWidth);
			
			ImGui.BeginDisabled(polygonCollider.VertexCount >= 8);

			if (ImGui.SmallButton("+"))
				polygonCollider.VertexCount++;

			ImGui.AttachTooltip("Add a new vertex to the collider.");

			ImGui.EndDisabled();

			ImGui.SameLine(ImGui.GetContentRegionAvail().x - removeButtonWidth);

			ImGui.BeginDisabled(polygonCollider.VertexCount <= 3);

			if (ImGui.SmallButton("-"))
				polygonCollider.VertexCount--;

			ImGui.AttachTooltip("Remove the last vertex from the collider.");

			ImGui.EndDisabled();

			// If isOpen is true, show list of vertices
			if (isOpen)
			{
				ImGui.PropertyTableStartNewProperty("Show Vertex gizmos");
				ImGui.Checkbox("##Show Vertex gizmos", &_editVerticesPolygonCollider2D);

				for (int i < polygonCollider.VertexCount)
				{
					ImGui.PropertyTableStartNewProperty(scope $"{i}");
					ImGui.Float2Editor(scope $"##{i}", ref polygonCollider.Vertices[i]);
				}

				ImGui.TreePop();
			}

			float density = polygonCollider.Density;
			ImGui.PropertyTableStartNewProperty("Density");
			if (ImGui.DragFloat("##Density", &density, 0.0f, 0.1f, format: "%.3g"))
				polygonCollider.Density = density;
			
			float friction = polygonCollider.Friction;
			ImGui.PropertyTableStartNewProperty("Friction");
			if (ImGui.DragFloat("##Friction", &friction, 0.0f, 0.1f, format: "%.3g"))
				polygonCollider.Friction = friction;

			float restitution = polygonCollider.Restitution;
			ImGui.PropertyTableStartNewProperty("Restitution");
			if (ImGui.DragFloat("##Restitution", &restitution, 0.0f, 0.1f, format: "%.3g"))
				polygonCollider.Restitution = restitution;

			float restitutionThreshold = polygonCollider.RestitutionThreshold;
			ImGui.PropertyTableStartNewProperty("Restitution Threshold");
			if (ImGui.DragFloat("##RestitutionThreshold", &restitutionThreshold, 0.0f, 0.1f, format: "%.3g"))
				polygonCollider.RestitutionThreshold = restitutionThreshold;
		}

		private static void ShowScriptComponentEditor(Entity entity, ScriptComponent* scriptComponent)
		{
			static char8[64] buffer = .();

			StringView search = StringView();

			char8* scriptLabel = scriptComponent.ScriptClassName.ToScopeCStr!() ?? "Select Script...";
			
			ImGui.PropertyTableStartNewProperty("Script");
			if (ImGui.Button(scriptLabel))
				ImGui.OpenPopup("SelectScript");

			if (ImGui.BeginPopup("SelectScript"))
			{
				ImGui.InputText("##ScriptSearch", &buffer, buffer.Count);

				search = StringView(&buffer);

				for (let (className, scriptClass) in ScriptEngine.EntityClasses)
				{
					if (!search.IsWhiteSpace && !className.Contains(search, true))
						continue;

					if (ImGui.Selectable(className.ToScopeCStr!(),
						className == scriptComponent.ScriptClassName))
					{
						scriptComponent.ScriptClassName = scriptClass.FullName;
					}
				}
				ImGui.EndPopup();
			}

			ScriptEngine.ShowScriptEditor(entity, scriptComponent);
		}

		private static void ShowLightComponentEditor(Entity entity, LightComponent* lightComponent)
		{
			const String[?] strings = String[]("Directional", "Point", "Spot");

			var light = ref lightComponent.SceneLight;

			String typeName = strings[light.LightType.Underlying];
			
			ImGui.PropertyTableStartNewProperty("Type");
			if (ImGui.BeginCombo("##Type", typeName.CStr()))
			{
				for (int i = 0; i < 3; i++)
				{
					bool isSelected = (typeName == strings[i]);

					if (ImGui.Selectable(strings[i], isSelected))
					{
						light.LightType = (.)i;
					}

					if (isSelected)
						ImGui.SetItemDefaultFocus();
				}

				ImGui.EndCombo();
			}
			
			ColorRGB color = ColorRGB.LinearToSRGB(light.Color);
			ImGui.PropertyTableStartNewProperty("Color");
			if (ImGui.ColorEdit3("##Color", ref color))
				light.Color = ColorRGB.SRgbToLinear(color);

			float illuminance = light.Illuminance;
			ImGui.PropertyTableStartNewProperty("Illuminance");
			if (ImGui.DragFloat("##Illuminance", &illuminance, 0.1f, 0.0f, float.MaxValue, format: "%.3g"))
				light.Illuminance = illuminance;
		}

		private static void ShowMeshComponentEditor(Entity entity, MeshComponent* meshComponent)
		{
			ImGui.PropertyTableStartNewProperty("Mesh");
			
			ShowAssetDropTarget<GeometryBinding>(ref meshComponent.Mesh);
		}

		private static void ShowAddComponentButton(Entity entity)
		{
			static char8[128] searchBuffer = .();
			static StringView searchFilter = .();
			
			static float buttonWidth = 100;

			ImGui.Separator();
			ImGui.NewLine();

			void ShowComponentButton<TComponent>(String name) where TComponent : struct, new
			{
				// If the entity already has this component, don't show the option to add it
				if (entity.HasComponent<TComponent>())
					return;

				float textWidth = ImGui.CalcTextSize(name.CStr()).x;
				buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);

				if (name.Contains(searchFilter, true) && ImGui.Selectable(name))
				{
					if (!entity.HasComponent<TComponent>())
						entity.AddComponent<TComponent>();
				}
			}

			float textWidth = ImGui.CalcTextSize("Add Component...").x;
			buttonWidth = Math.Max(buttonWidth, textWidth + ImGui.GetStyle().FramePadding.x * 2);

			ImGui.PushItemWidth(buttonWidth);

			ImGui.NewLine();
			ImGui.SameLine(ImGui.GetContentRegionMax().x / 2 - buttonWidth / 2);

			if (ImGui.BeginCombo("##add_component_combo", "Add Component...", .NoArrowButton))
			{
				if (ImGui.InputText("##search_component_name", &searchBuffer, (.)searchBuffer.Count))
				{
					searchFilter = .(&searchBuffer);
				}

				ImGui.Separator();

				ShowComponentButton<CameraComponent>("Camera");
				ShowComponentButton<SpriteRendererComponent>("Sprite Renderer");
				ShowComponentButton<CircleRendererComponent>("Circle Renderer");
				ShowComponentButton<TextRendererComponent>("Text Renderer");
				ShowComponentButton<LightComponent>("Light");
				ShowComponentButton<Rigidbody2DComponent>("Rigidbody 2D");
				ShowComponentButton<BoxCollider2DComponent>("Box collider 2D");
				ShowComponentButton<CircleCollider2DComponent>("Circle collider 2D");
				ShowComponentButton<PolygonCollider2DComponent>("Polygon collider 2D");
				ShowComponentButton<MeshComponent>("Mesh");
				ShowComponentButton<MeshRendererComponent>("Mesh Renderer");
				ShowComponentButton<ScriptComponent>("C# Script");

				ImGui.EndCombo();
			}

			ImGui.PopItemWidth();
		}
	}
}
