Items/Tree.cs
using System;
using System.Threading.Tasks;
using Braxnet;
using Clover.Data;
using Clover.Interactable;
using Clover.Persistence;
using Clover.Player;

namespace Clover.Items;

[Category( "Clover/Items" )]
public class Tree : Component, IPersistent, IInteract
{
	[RequireComponent] public WorldItem WorldItem { get; set; }

	[Property] public List<GameObject> GrowSpawnPoints { get; set; } = new();
	[Property] public List<GameObject> ShakeSpawnPoints { get; set; } = new();

	[Property] public GameObject TreeModel { get; set; }
	[Property] public GameObject StumpModel { get; set; }

	[Property] public FruitData FruitData { get; set; }

	[Property] public SoundEvent ShakeSound { get; set; }
	[Property] public SoundEvent FallSound { get; set; }
	[Property] public SoundEvent FallGroundSound { get; set; }
	[Property] public SoundEvent FruitHitGroundSound { get; set; }

	[Property] public ItemData StumpData { get; set; }

	public DateTime LastFruitDrop { get; set; } = DateTime.UnixEpoch;

	public bool IsDroppingFruit;
	public bool IsFalling;
	public bool IsShaking;

	public const float FruitGrowTime = 10f;

	private bool _hasFruit;


	protected override void OnAwake()
	{
		if ( IsProxy ) return;
		base.OnAwake();
		if ( StumpModel.IsValid() ) StumpModel.Enabled = false;
	}

	private void SpawnFruit()
	{
		if ( _hasFruit ) return;
		if ( !FruitData.IsValid() ) throw new Exception( "FruitData is null" );
		foreach ( var spawnPoint in GrowSpawnPoints )
		{
			var scene = FruitData.InTreeScene;
			if ( scene == null )
			{
				throw new Exception( "FruitData.InTreeScene is null" );
			}

			// var fruit = scene.Instantiate<Node3D>();
			// spawnPoint.AddChild( fruit );
			// Logger.Debug( "Tree", "Added fruit to tree" );
			var fruit = FruitData.InTreeScene.Clone();
			fruit.SetParent( spawnPoint );
			fruit.LocalPosition = Vector3.Zero;
		}

		_hasFruit = true;
	}

	private async void Shake()
	{
		if ( IsShaking ) return;

		IsShaking = true;

		/*var tween = GetTree().CreateTween();
		tween.TweenProperty( Model, "rotation_degrees", new Vector3( 0, 0, 5 ), 0.2f ).SetTrans( Tween.TransitionType.Quad ).SetEase( Tween.EaseType.In );
		tween.TweenProperty( Model, "rotation_degrees", new Vector3( 0, 0, -5 ), 0.2f ).SetTrans( Tween.TransitionType.Quad ).SetEase( Tween.EaseType.InOut );
		tween.TweenProperty( Model, "rotation_degrees", new Vector3( 0, 0, 5 ), 0.2f ).SetTrans( Tween.TransitionType.Quad ).SetEase( Tween.EaseType.InOut );
		tween.TweenProperty( Model, "rotation_degrees", new Vector3( 0, 0, 0 ), 0.2f ).SetTrans( Tween.TransitionType.Quad ).SetEase( Tween.EaseType.Out );
		await ToSignal( tween, Tween.SignalName.Finished );*/

		var tween = TweenManager.CreateTween();
		tween.AddRotation( TreeModel, Rotation.FromRoll( 5 ), 0.2f ).SetEasing( Sandbox.Utility.Easing.QuadraticOut );
		tween.AddRotation( TreeModel, Rotation.FromRoll( -5 ), 0.2f )
			.SetEasing( Sandbox.Utility.Easing.QuadraticInOut );
		tween.AddRotation( TreeModel, Rotation.FromRoll( 5 ), 0.2f ).SetEasing( Sandbox.Utility.Easing.QuadraticInOut );
		tween.AddRotation( TreeModel, Rotation.FromRoll( 0 ), 0.2f ).SetEasing( Sandbox.Utility.Easing.QuadraticOut );
		await tween.Wait();

		await DropFruitAsync();

		IsShaking = false;
	}

	public async Task DropFruitAsync()
	{
		if ( IsDroppingFruit ) return;
		IsDroppingFruit = true;
		for ( var i = 0; i < GrowSpawnPoints.Count; i++ )
		{
			var growPoint = GrowSpawnPoints[i];
			var shakePoint = ShakeSpawnPoints[i];

			/* foreach ( var child in growPoint.GetChildren() )
			{ */

			// Log.Info( $"Checking growPoint: {growPoint.Name}" );

			var growNodeRaw = growPoint.Children.FirstOrDefault();
			var shakeNodeRaw = shakePoint;

			var endPos = shakeNodeRaw.WorldPosition + Vector3.Up * 8f;

			// if ( child is not Node3D growNode ) continue;
			/*var tween = GetTree().CreateTween();
			var p = tween.TweenProperty( growNode, "global_position", endPos, 0.7f + GD.Randf() * 0.5f );
			p.SetTrans( Tween.TransitionType.Bounce );
			p.SetEase( Tween.EaseType.Out );
			p.SetDelay( GD.Randf() * 0.5f );

			tween.TweenCallback( Callable.From( () =>
			{
				growNode.QueueFree();
				var pos = World.WorldToItemGrid( shakePoint.GlobalTransform.Origin );
				World.SpawnNode( FruitData, pos, World.ItemRotation.North, World.ItemPlacement.Floor, true );

				GetNode<AudioStreamPlayer3D>( "Drop" ).Play();
			} ) );*/

			var tween = TweenManager.CreateTween();
			var p = tween.AddPosition( growNodeRaw, endPos, 0.7f + Random.Shared.Float() * 0.5f );
			p.SetEasing( Sandbox.Utility.Easing.BounceOut );
			p.SetDelay( Random.Shared.Float() * 0.5f );

			p.OnFinish += () =>
			{
				Sound.Play( FruitHitGroundSound, shakePoint.WorldPosition );
			};

			p.OnBounce += () =>
			{
				Log.Info( "Bounce" );
				Sound.Play( FruitHitGroundSound, shakePoint.WorldPosition );
			};

			/* await ToSignal( tween, Tween.SignalName.Finished );

			growNode.QueueFree();
			var pos = World.WorldToItemGrid( shakePoint.GlobalTransform.Origin );
			World.SpawnNode( FruitData, pos, World.ItemRotation.North, World.ItemPlacement.Floor );

			GetNode<AudioStreamPlayer3D>( "Drop" ).Play(); */

			/*tween.Wait().ContinueWith( _ =>
			{
				Log.Info( "Tween finished" );

				growNode?.Destroy();

				// TODO: check free space
				var pos = WorldItem.WorldLayerObject.World.WorldToItemGrid( shakePoint.WorldPosition );
				WorldItem.WorldLayerObject.World.SpawnDroppedNode( FruitData, pos, World.ItemRotation.North,
					World.ItemPlacement.Floor );

				Sound.Play( FruitHitGroundSound, shakePoint.WorldPosition );

			} );*/
		}

		await Task.DelayRealtimeSeconds( 1.5f );

		foreach ( var growPoint in GrowSpawnPoints )
		{
			foreach ( var child in growPoint.Children )
			{
				child.Destroy();
			}
		}

		foreach ( var shakePoint in ShakeSpawnPoints )
		{
			var pos = WorldItem.WorldLayerObject.World.WorldToItemGrid( shakePoint.WorldPosition );
			WorldItem.WorldLayerObject.World.SpawnDroppedNode( FruitData, pos, World.ItemRotation.North );
		}

		// await ToSignal( GetTree().CreateTimer( 1.5f ), Timer.SignalName.Timeout );
		// await Task.DelayRealtimeSeconds( 1.5f );
		IsDroppingFruit = false;
		LastFruitDrop = DateTime.Now;
		_hasFruit = false;
	}

	private void CheckGrowth()
	{
		if ( IsFalling ) return;
		if ( GrowSpawnPoints == null || GrowSpawnPoints.Count == 0 ) return;
		if ( ShakeSpawnPoints == null || ShakeSpawnPoints.Count == 0 ) return;

		if ( !_hasFruit && DateTime.Now - LastFruitDrop > TimeSpan.FromSeconds( FruitGrowTime ) )
		{
			SpawnFruit();
			// LastFruitDrop = DateTime.Now;
		}
	}

	protected override void OnFixedUpdate()
	{
		if ( IsProxy ) return;
		if ( FruitData == null ) return;
		CheckGrowth();
	}


	public void OnSave( PersistentItem item )
	{
		item.SetSaveData( "LastFruitDrop", LastFruitDrop );
	}

	public void OnLoad( PersistentItem item )
	{
		LastFruitDrop = item.GetSaveData<DateTime>( "LastFruitDrop" );
	}

	protected override void DrawGizmos()
	{
		base.DrawGizmos();

		foreach ( var growPoint in GrowSpawnPoints )
		{
			if ( !growPoint.IsValid() ) continue;
			Gizmo.Draw.LineSphere( growPoint.LocalPosition, 8f );
		}

		foreach ( var shakePoint in ShakeSpawnPoints )
		{
			if ( !shakePoint.IsValid() ) continue;
			Gizmo.Draw.LineSphere( shakePoint.LocalPosition, 8f );
		}
	}

	public void StartInteract( PlayerCharacter player )
	{
		if ( FruitData == null ) return;
		Shake();
	}

	public void FinishInteract( PlayerCharacter player )
	{
	}

	public string GetInteractName()
	{
		return "Shake";
	}
}