using System.Reflection;
using Engine.Scene.Component;
using Engine.Scene.Component.BuiltIn;

namespace Engine.Scene;

public sealed class GameObject : IUpdate, IRender
{
  public Guid Id { get; } = Guid.NewGuid();

  public bool IsEnabled
  {
    get => IsSelfEnabled && IsParentEnabled;
    set => _nextIsSelfEnabled = value;
  }

  private bool IsSelfEnabled { get; set; } = true;
  private bool _prevIsSelfEnabled = true;
  private bool _nextIsSelfEnabled = true;
  private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true;

  public Transform Transform { get; }

  public Scene? Scene { get; set; }

  private readonly Queue<Action> _componentActions = new();

  private readonly List<Component.Component> _components = [];
  private readonly HashSet<Type> _addedComponentTypes = [];

  private readonly HashSet<Component.Component> _addedComponents = [];
  private readonly HashSet<Component.Component> _removedComponents = [];

  public GameObject()
  {
    AddComponent<Transform>();
    ProcessChanges();

    Transform = GetComponent<Transform>()!;
  }

  public GameObject(Transform parTransform)
  {
    AddComponent(parTransform.Clone());
    ProcessChanges();

    Transform = GetComponent<Transform>()!;
  }

  public void PreUpdate()
  {
    ProcessAddedComponents();
    ProcessRemovedComponents();

    foreach (var component in _components)
    {
      component.PreUpdate();
    }
  }

  private void ProcessAddedComponents()
  {
    foreach (var component in _addedComponents)
    {
      component.Awake();
      component.Start();
    }

    _addedComponents.Clear();
  }

  private void ProcessRemovedComponents()
  {
    foreach (var component in _removedComponents)
    {
      component.Destroy();
      component.GameObject = null!;
    }

    _removedComponents.Clear();
  }

  public void Update(double parDeltaTime)
  {
    if (!IsEnabled)
    {
      if (!_prevIsSelfEnabled)
        return;

      foreach (var component in _components)
      {
        component.Disable();
      }

      _prevIsSelfEnabled = false;
      return;
    }

    if (!_prevIsSelfEnabled)
    {
      foreach (var component in _components)
      {
        component.Enable();
      }
    }

    _prevIsSelfEnabled = true;

    foreach (var component in _components)
    {
      component.Update(parDeltaTime);
    }
  }

  public void Render()
  {
    if (!IsEnabled)
      return;

    foreach (var component in _components)
    {
      component.Render();
    }
  }

  public void Destroy()
  {
    foreach (var component in _components)
    {
      component.Destroy();
    }
  }

  public T? GetComponent<T>() where T : Component.Component
  {
    if (!HasComponent<T>())
      return null;

    foreach (var component in _components)
    {
      if (component is T result)
        return result;
    }

    return null;
  }

  public T? GetComponentAny<T>() where T : Component.Component
  {
    var component = GetComponent<T>();
    if (component != null)
      return component;

    component = GetComponentInChildren<T>();
    return component;
  }

  public T? GetComponentInChildren<T>() where T : Component.Component
  {
    var children = Scene!.Hierarchy.GetChildren(this);

    foreach (var child in children)
    {
      var component = child.GetComponent<T>();
      if (component != null)
        return component;

      var childComponent = child.GetComponentInChildren<T>();
      if (childComponent != null)
        return childComponent;
    }

    return null;
  }

  public void AddComponent<T>() where T : Component.Component, new()
  {
    var component = new T();
    AddComponent(component);
  }

  public void AddComponent<T>(params object?[] parArgs) where T : Component.Component
  {
    var component = (T?)Activator.CreateInstance(
      typeof(T),
      BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance |
      BindingFlags.OptionalParamBinding,
      null,
      parArgs,
      null
    );

    if (component == null)
    {
      throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
    }

    AddComponent(component);
  }

  public void AddComponent<T>(T parComponent) where T : Component.Component
  {
    parComponent.GameObject = this;

    _componentActions.Enqueue(() =>
    {
      if (HasComponent<T>())
      {
        throw new ArgumentException($"GameObject already has component of type {typeof(T)}");
      }

      _components.Add(parComponent);
      _addedComponentTypes.Add(parComponent.GetType().GetComponentBaseType());
      _addedComponents.Add(parComponent);
    });
  }

  public void RemoveComponent<T>() where T : Component.Component
  {
    if (typeof(T) == typeof(Transform))
    {
      throw new ArgumentException("GameObject cannot remove Transform component");
    }

    _componentActions.Enqueue(() =>
    {
      if (!HasComponent<T>())
      {
        throw new ArgumentException($"GameObject does not have component of type {typeof(T)}");
      }

      var component = GetComponent<T>();
      if (component == null)
      {
        return;
      }

      _components.Remove(component);
      _addedComponentTypes.Remove(typeof(T));
      _removedComponents.Add(component);
    });
  }

  public bool HasComponent<T>() where T : Component.Component
  {
    var baseType = typeof(T).GetComponentBaseType();
    return _addedComponentTypes.Contains(baseType);
  }

  internal void ProcessChanges()
  {
    IsSelfEnabled = _nextIsSelfEnabled;

    while (_componentActions.TryDequeue(out var action))
    {
      action();
    }
  }

  public override string ToString()
  {
    return Id.ToString();
  }

  public override bool Equals(object? parObj)
  {
    return parObj is GameObject gameObject && Id == gameObject.Id;
  }

  public override int GetHashCode()
  {
    return HashCode.Combine(Id);
  }
}