Actors/ActorMechanic.cs
namespace Opium;

public abstract class ActorMechanic : Component
{
	[Property, Group( "Base" )] public Actor Actor { get; set; }
	[Property, Group( "Base" )] public int Priority { get; set; } = 0;
	[Property, Group( "Base" ), ReadOnly] public TimeSince TimeSinceActiveChanged { get; set; } = 1f;

	private bool isActive;
	/// <summary>
	/// Is this mechanic active?
	/// </summary>
	[Property, Group( "Base" ), ReadOnly]
	public bool IsActive
	{
		get => isActive;
		set
		{
			var before = isActive;
			isActive = value;

			if ( isActive != before )
			{
				TimeSinceActiveChanged = 0;
				OnActiveChanged( before, isActive );
			}
		}
	}

	/// <summary>
	/// Try to forcefully activate the mechanic
	/// </summary>
	public virtual void Activate()
	{	
		IsActive = true;
	}

	/// <summary>
	/// Try to forcefully deactivate the mechanic
	/// </summary>
	public virtual void Deactivate()
	{
		IsActive = false;
	}

	protected override void OnAwake()
	{
		// If we don't have the player controller defined, let's have a look for it
		if ( !Actor.IsValid() )
		{
			Actor = Components.Get<Actor>( FindMode.EverythingInSelfAndAncestors );
		}
	}

	/// <summary>
	/// Return a list of tags to be used by the player controller / other mechanics.
	/// </summary>
	/// <returns></returns>
	public virtual IEnumerable<string> GetTags()
	{
		return Enumerable.Empty<string>();
	}

	/// <summary>
	/// An accessor to see if the player controller has a tag.
	/// </summary>
	/// <param name="tag"></param>
	/// <returns></returns>
	public bool HasTag( string tag ) => Actor.MechanicTags.Has( tag );

	/// <summary>
	/// An accessor to see if the player controller has all matched tags.
	/// </summary>
	/// <param name="tags"></param>
	/// <returns></returns>
	public bool HasAllTags( ITagSet tags ) => Actor.MechanicTags.HasAll( tags );

	/// <inheritdoc cref="HasAllTags(ITagSet)"/>
	public bool HasAllTags( params string[] tags )
	{
		var set = new TagSet();
		foreach ( var tag in tags )
		{
			set.Add( tag );
		}
		return HasAllTags( set );
	}

	/// <summary>
	/// An accessor to see if the player controller has any tag.
	/// </summary>
	/// <param name="tags"></param>
	/// <returns></returns>
	public bool HasAnyTag( ITagSet tags ) => Actor.Tags.HasAny( tags );
	
	/// <inheritdoc cref="HasAnyTag(ITagSet)"/>
	public bool HasAnyTag( params string[] tags )
	{
		var set = new TagSet();
		foreach ( var tag in tags )
		{
			set.Add( tag );
		}
		return HasAnyTag( set );
	}

	/// <summary>
	/// Called when <see cref="IsActive"/> changes.
	/// </summary>
	/// <param name="before"></param>
	/// <param name="after"></param>
	protected virtual void OnActiveChanged( bool before, bool after )
	{
		//
	}

	/// <summary>
	/// Called by <see cref="Opium.PlayerController"/>, treat this like a Tick/Update while the mechanic is active.
	/// </summary>
	public virtual void OnActiveUpdate()
	{
		//
	}

	/// <summary>
	/// Should we be ticking this mechanic at all?
	/// </summary>
	/// <returns></returns>
	public virtual bool ShouldBecomeActive()
	{
		return false;
	}

	/// <summary>
	/// Should we be inactive?
	/// </summary>
	/// <returns></returns>
	public virtual bool ShouldBecomeInactive()
	{
		return !ShouldBecomeActive();
	}

	/// <summary>
	/// Mechanics can override the player's movement speed.
	/// </summary>
	/// <returns></returns>
	public virtual float? GetSpeed()
	{
		return null;
	}

	/// <summary>
	/// Mechanics can override the player's eye height.
	/// </summary>
	/// <returns></returns>
	public virtual float? GetEyeHeight()
	{
		return null;
	}

	/// <summary>
	/// Mechanics can override the player's ground friction.
	/// </summary>
	public virtual float? GetGroundFriction()
	{
		return null;
	}

	/// <summary>
	/// Mechanics can override the player's acceleration.
	/// </summary>
	/// <returns></returns>
	public virtual float? GetAcceleration()
	{
		return null;
	}

	/// <summary>
	/// Do we lock movement?
	/// </summary>
	public virtual bool LockMovement { get; } = false;


	/// <summary>
	/// Do we lock mouse movement?
	/// </summary>
	public virtual bool LockMouseMovement { get; } = false;

	/// <summary>
	/// Mechanics can override the player's wish input direction.
	/// </summary>
	public virtual void BuildWishInput( ref Vector3 wish )
	{
	}

	public virtual void OnEvent( string eventName, params object[] obj )
	{
	}
}