Components/Equips.cs
using System;
using Clover.Carriable;
using Clover.Player;

namespace Clover.Components;

/// <summary>
///  A component that manages equipping items to players and NPCs.
/// </summary>
[Title( "Equips" )]
[Icon( "build" )]
[Category( "Clover/Components" )]
public class Equips : Component
{
	[Flags]
	public enum EquipSlot
	{
		None = 1 << 0, // Not a valid slot

		[Icon( "hat" )] Hat = 1 << 1,

		[Icon( "tshirt" )] Top = 1 << 2,

		[Icon( "jeans" )] Bottom = 1 << 3,

		[Icon( "ice_skating" )] Shoes = 1 << 4,

		[Icon( "build" )] Tool = 1 << 5,

		[Icon( "face_2" )] Hair = 1 << 6,
		// TODO: add more later?
	}

	[Property] public Dictionary<EquipSlot, GameObject> AttachPoints { get; set; } = new();

	public Dictionary<EquipSlot, GameObject> EquippedItems { get; set; } = new();

	[Property] public Action<EquipSlot, GameObject> OnEquippedItemChanged { get; set; }

	[Property] public Action<EquipSlot> OnEquippedItemRemoved { get; set; }

	[Sync] public NetDictionary<EquipSlot, bool> EquippedSlots { get; set; } = new();
	[Sync] public NetDictionary<EquipSlot, bool> VisibleSlots { get; set; } = new();
	
	private PlayerCharacter Player => GetComponent<PlayerCharacter>();

	public GameObject GetEquippedItem( EquipSlot slot )
	{
		return CollectionExtensions.GetValueOrDefault( EquippedItems, slot );
	}

	public T GetEquippedItem<T>( EquipSlot slot ) where T : Component
	{
		return EquippedItems.TryGetValue( slot, out var gameObject ) ? gameObject.GetComponent<T>() : null;
	}

	public bool IsEquippedItemType<T>( EquipSlot slot )
	{
		return EquippedItems.TryGetValue( slot, out var gameObject ) && gameObject.GetComponent<T>() != null;
	}

	public bool HasEquippedItem( EquipSlot slot )
	{
		return EquippedItems.ContainsKey( slot ) && EquippedItems[slot].IsValid();
	}

	public bool HasEquippedItem<T>( EquipSlot slot ) where T : Component
	{
		return EquippedItems.TryGetValue( slot, out var gameObject ) && gameObject.GetComponent<T>() != null;
	}

	public bool TryGetEquippedItem( EquipSlot slot, out GameObject item )
	{
		return EquippedItems.TryGetValue( slot, out item );
	}

	public bool TryGetEquippedItem<T>( EquipSlot slot, out T item ) where T : Component
	{
		if ( EquippedItems.TryGetValue( slot, out var gameObject ) )
		{
			item = gameObject.GetComponent<T>();
			return item.IsValid();
		}

		item = null;
		return false;
	}

	public void SetEquippedItem( EquipSlot slot, GameObject item )
	{
		if ( slot == 0 ) throw new ArgumentException( "Invalid slot" );
		if ( !item.IsValid() ) throw new ArgumentException( "Item is not valid" );

		if ( !AttachPoints.TryGetValue( slot, out var attachPoint ) )
		{
			throw new ArgumentException( $"No attach point for slot {slot}" );
		}

		if ( EquippedItems.ContainsKey( slot ) )
		{
			RemoveEquippedItem( slot, true );
		}

		EquippedItems[slot] = item;

		EquippedSlots[slot] = true;

		item.SetParent( attachPoint );
		item.LocalPosition = Vector3.Zero;
		item.LocalRotation = Rotation.Identity;

		item.NetworkSpawn();

		if ( item.Components.TryGet<BaseCarriable>( out var carriable ) )
		{
			carriable.SetHolder( GameObject );
			carriable.OnEquip( GameObject ); // TODO: don't hardcode player, since NPCs can use items too
			carriable.OnEquipAction?.Invoke( GameObject );
		}

		OnEquippedItemChanged?.Invoke( slot, item );
		Scene.RunEvent<IEquipChanged>( x => x.OnEquippedItemChanged( GameObject, slot, item ) );
	}

	public void RemoveEquippedItem( EquipSlot slot, bool destroy = false )
	{
		if ( EquippedItems.ContainsKey( slot ) )
		{
			if ( destroy ) EquippedItems[slot].Destroy();
			EquippedItems.Remove( slot );
			EquippedSlots[slot] = false;
			OnEquippedItemRemoved?.Invoke( slot );
			Scene.RunEvent<IEquipChanged>( x => x.OnEquippedItemRemoved( GameObject, slot ) );
		}
	}

	public void SetEquippableVisibility( EquipSlot slot, bool visible )
	{
		if ( EquippedItems.ContainsKey( slot ) )
		{
			EquippedItems[slot].Enabled = visible;
			VisibleSlots[slot] = visible;
			return;
		}

		throw new Exception( $"No item equipped in slot {slot}" );
	}
	
	public bool CanUseTool()
	{
		if ( Player.ItemPlacer.IsPlacing || Player.ItemPlacer.IsMoving ) return false;
		return true;
	}

	protected override void OnFixedUpdate()
	{
		if ( IsProxy ) return;

		if ( !CanUseTool() ) return;

		if ( Input.Pressed( "UseTool" ) )
		{
			if ( HasEquippedItem( EquipSlot.Tool ) )
			{
				var tool = GetEquippedItem<BaseCarriable>( EquipSlot.Tool );
				if ( tool.CanUse() )
				{
					// tool.OnUse( GetComponent<PlayerCharacter>() );
					tool.OnUseDown();
					tool.OnUseDownAction?.Invoke();

					using ( Rpc.FilterInclude( Connection.Host ) )
					{
						tool.OnUseDownHost();
					}
				}
				else
				{
					Log.Info( "Can't use tool" );
				}
			}
			else
			{
				Log.Info( "No tool equipped" );
			}
		}
		else if ( Input.Released( "UseTool" ) )
		{
			if ( HasEquippedItem( EquipSlot.Tool ) )
			{
				var tool = GetEquippedItem<BaseCarriable>( EquipSlot.Tool );
				tool.OnUseUp();
				tool.OnUseUpAction?.Invoke();
			}
		}
	}

	protected override void DrawGizmos()
	{
		foreach ( var pair in AttachPoints )
		{
			if ( pair.Value.IsValid() )
			{
				Gizmo.Draw.LineSphere( pair.Value.WorldPosition, 4f );
				Gizmo.Draw.Arrow( pair.Value.WorldPosition,
					pair.Value.WorldPosition + pair.Value.WorldRotation.Up * 32f );
				Gizmo.Draw.Arrow( pair.Value.WorldPosition,
					pair.Value.WorldPosition + pair.Value.WorldRotation.Forward * 32f );
			}
		}
	}
}

public interface IEquipChanged
{
	void OnEquippedItemChanged( GameObject owner, Equips.EquipSlot slot, GameObject item );
	void OnEquippedItemRemoved( GameObject owner, Equips.EquipSlot slot );
}