Carriable/Shovel.cs
using Clover.Data;
using Clover.Inventory;
using Clover.Items;
using Clover.Persistence;

namespace Clover.Carriable;

[Category( "Clover/Carriable" )]
public class Shovel : BaseCarriable
{
	[Property] public SoundEvent HitSound { get; set; }
	[Property] public SoundEvent DigSound { get; set; }
	[Property] public SoundEvent FillSound { get; set; }


	public override void OnUseDown()
	{
		if ( !CanUse() ) return;

		NextUse = 1f;

		/*if ( !Networking.IsHost )
		{
			Log.Error( "Only the host can use world altering items for now." );
			return;
		}*/
	}

	[Rpc.Broadcast]
	public override void OnUseDownHost()
	{
		var pos = Player.GetAimingGridPosition();

		var worldPos = Player.World.ItemGridToWorld( pos );

		if ( !CanDigAt( worldPos ) )
		{
			Log.Warning( "Can't dig here." );
			return;
		}

		var worldItems = Player.World.GetItems( pos ).ToList();

		if ( worldItems.Count == 0 )
		{
			DigHole( pos );
			return;
		}
		else
		{
			/*var undergroundItem = worldItems.FirstOrDefault( x => x.GridPlacement == World.ItemPlacement.Underground );
			if ( undergroundItem != null )
			{
				DigUpItem( pos, undergroundItem );
				return;
			}*/

			foreach ( var floorItem in worldItems )
			{
				if ( floorItem.Components.TryGet<Hole>( out var hole ) )
				{
					FillHole( pos );
					break;
				}
				else if ( floorItem.Components.TryGet<IDiggable>( out var diggable ) )
				{
					DigUpFloorItem( pos, floorItem, diggable );
					break;
				}
				else
				{
					HitItem( pos, floorItem );
					break;
				}
			}
		}

		Log.Warning( "No action taken." );
	}

	/*public override void OnUseUp()
	{
		base.OnUseUp();
	}*/

	private bool CanDigAt( Vector3 worldPos )
	{
		var hitbox = Player.PlayerInteract.InteractCollider;

		if ( hitbox.Touching.FirstOrDefault( x => x.GameObject != Holder && x.GameObject.Tags.Has( "player" ) ) !=
		     null )
		{
			Log.Warning( "Can't dig while touching player." );
			return false;
		}

		var trace = Scene.Trace.Ray( worldPos + Vector3.Up * 16f, worldPos + Vector3.Down * 32f )
			.WithTag( "terrain" )
			.Run();

		if ( !trace.Hit ) return false;

		var surface = trace.Surface;

		return surface.ResourceName == "grass" || surface.ResourceName == "dirt";
	}

	private void HitItem( Vector2Int pos, WorldItem floorItem )
	{
		if ( floorItem.ItemData.Name == "Tree stump" )
		{
			floorItem.RemoveFromWorld();
			return;
		}

		Log.Info( $"Hit {floorItem.ItemData.Name} at {pos}" );
	}

	private void DigHole( Vector2Int pos )
	{
		Log.Info( $"Dug hole at {pos}" );

		Player.SnapToGrid();
		Player.ModelLookAt( Player.World.ItemGridToWorld( pos ) );

		var holeData = Data.ItemData.Get( "hole" );
		var hole = Player.World.SpawnPlacedItem( holeData, pos, World.ItemRotation.North );

		SoundEx.Play( DigSound, WorldPosition );

		Durability--;
	}

	private void FillHole( Vector2Int pos )
	{
		Log.Info( $"Filled hole at {pos}" );

		var hole = Player.World.GetWorldItem<Hole>( pos );
		if ( hole == null )
		{
			Log.Info( "No hole found." );
			return;
		}

		if ( hole.Components.TryGet<Hole>( out var holeItem ) )
		{
			// Player.World.RemoveItem( holeItem.GameObject );
			holeItem.GetComponent<WorldItem>().RemoveFromWorld();

			Player.SnapToGrid();
			Player.ModelLookAt( Player.World.ItemGridToWorld( pos ) );

			Durability--;

			SoundEx.Play( FillSound, WorldPosition );
		}
		else
		{
			Log.Warning( "Not a hole." );
		}

		// TODO: check if hole has item in it
	}

	/*private void DigUpItem( Vector2Int pos, WorldNodeLink item )
	{
		Log.Info( $"Dug up {item.ItemData.Name} at {pos}" );

		// var inventoryItem = PersistentItem.Create( item );
		item.RunSavePersistence();
		var inventoryItem = item.GetPersistence();

		try
		{
			Player.Inventory.PickUpItem( inventoryItem );
		}
		catch ( InventoryFullException e )
		{
			Log.Warning( e.Message );
			return;
		}
		catch ( System.Exception e )
		{
			Log.Error( e.Message );
			return;
		}

		Player.World.RemoveItem( item );

		var dirtQuery = Player.World.GetItems( pos ).ToList();
		/*if ( dirt != null && dirt.ItemData?.ResourceName == "buried_item" )
		{
			Player.World.RemoveItem( dirt );
		}#1#

		if ( dirtQuery.Count > 0 )
		{
			foreach ( var dirt in dirtQuery.Where( dirt => dirt.ItemData.ResourceName == "buried_item" ) )
			{
				Player.World.RemoveItem( dirt );
			}
		}

		DigHole( pos );
	}*/

	/// <summary>
	///  Normally you can only dig up items that are placed underground, but some items like flowers and tree stumps are placed on the floor.
	/// </summary>
	/// <param name="pos"></param>
	/// <param name="item"></param>
	private void DigUpFloorItem( Vector2Int pos, WorldItem item, IDiggable diggable )
	{
		Log.Info( $"Dug up {item.ItemData.Name} at {pos}" );

		/*if ( giveItem )
		{
			// var inventoryItem = PersistentItem.Create( item );

			item.RefreshPersistence();
			var inventoryItem = item.GetPersistence();

			try
			{
				Player.Inventory.PickUpItem( inventoryItem );
			}
			catch ( InventoryFullException e )
			{
				Log.Warning( e.Message );
				return;
			}
			catch ( System.Exception e )
			{
				Log.Error( e.Message );
				return;
			}
		}*/

		if ( !diggable.CanDig() )
		{
			Log.Warning( "Can't dig up item." );
			return;
		}

		if ( !diggable.OnDig( Player, item ) )
		{
			Log.Warning( "Failed to dig up item." );
			return;
		}

		// Player.World.RemoveItem( item );
		item.RemoveFromWorld();

		DigHole( pos );
	}

	public void BuryItem( InventorySlot slot, WorldItem hole )
	{
		var gridPos = hole.GridPosition;

		// hole.RunSavePersistence();
		// var item = hole.Persistence;

		hole.RemoveFromWorld();

		// main item
		// Player.World.SpawnDroppedNode( slot.PersistentItem, gridPos, World.ItemRotation.North, World.ItemPlacement.Underground );

		// dirt
		var dirt = Player.World.SpawnPlacedItem( Data.ItemData.Get( "buried_item" ), gridPos,
			World.ItemRotation.North );

		var node = dirt;

		node.Components.Get<BuriedItem>().SetItem( slot.PersistentItem );
	}
}