using System.Collections.Generic;
using Fusion;
using UnityEngine;

namespace Projectiles
{
	public enum EHitAction : byte
	{
		None,
		Damage,
		Heal,
	}

	public struct HitData
	{
		public EHitAction     Action;
		public float          Amount;
		public bool           IsFatal;
		public Vector3        Position;
		public Vector3        Direction;
		public Vector3        Normal;
		public Vector3        RootPosition;
		public PlayerRef      InstigatorRef;
		public IHitInstigator Instigator;
		public IHitTarget     Target;
		public EHitType       HitType;
	}

	public enum EHitType
	{
		None,
		Projectile,
		Explosion,
		Suicide,
	}

	public interface IHitTarget
	{
		bool      IsActive    { get; }
		Transform HeadPivot   { get; }
		Transform BodyPivot   { get; }
		Transform GroundPivot { get; }
		Hitbox    BodyHitbox  { get; }

		void ProcessHit(ref HitData hit);
	}

	public interface IHitInstigator
	{
		void HitPerformed(HitData hit);
	}

	/// <summary>
	/// A utility that encapsulates common approach of handling hits.
	/// </summary>
	public static class HitUtility
	{
		// PUBLIC METHODS

		public static void GetAllTargets(NetworkRunner runner, List<IHitTarget> targets, bool onlyActive = true)
		{
			targets.Clear();

			var healths = ListPool.Get<Health>(targets.Count);

			// TODO: Best would be to get IHitTargets directly, but that is not possible for now
			runner.GetAllBehaviours(healths);

			if (onlyActive == true)
			{
				for (int i = 0; i < healths.Count; i++)
				{
					var target = healths[i] as IHitTarget;

					if (target.IsActive == true)
						targets.Add(target);
				}
			}
			else
			{
				targets.AddRange(healths);
			}

			ListPool.Return(healths);
		}

		public static HitData ProcessHit(PlayerRef instigatorRef, Vector3 direction, LagCompensatedHit hit, float baseDamage, EHitType hitType)
		{
			var target = GetHitTarget(hit.Hitbox, hit.Collider);
			if (target == null)
				return default;

			HitData hitData = default;

			hitData.Action        = EHitAction.Damage;
			hitData.Amount        = baseDamage;
			hitData.Position      = hit.Point;
			hitData.Normal        = hit.Normal;
			hitData.Direction     = direction;
			hitData.Target        = target;
			hitData.InstigatorRef = instigatorRef;
			hitData.HitType       = hitType;

			return ProcessHit(ref hitData);
		}

		public static HitData ProcessHit(NetworkBehaviour instigator, Vector3 direction, LagCompensatedHit hit, float baseDamage, EHitType hitType)
		{
			var target = GetHitTarget(hit.Hitbox, hit.Collider);
			if (target == null)
				return default;

			HitData hitData = default;

			hitData.Action        = EHitAction.Damage;
			hitData.Amount        = baseDamage;
			hitData.Position      = hit.Point;
			hitData.Normal        = hit.Normal;
			hitData.Direction     = direction;
			hitData.Target        = target;
			hitData.InstigatorRef = instigator != null ? instigator.Object.InputAuthority : default;
			hitData.Instigator    = instigator != null ? instigator.GetComponent<IHitInstigator>() : null;
			hitData.HitType       = hitType;

			return ProcessHit(ref hitData);
		}

		public static HitData ProcessHit(NetworkBehaviour instigator, Collider collider, float damage, EHitType hitType)
		{
			var target = GetHitTarget(null, collider);
			if (target == null)
				return default;

			HitData hitData = default;

			hitData.Action        = EHitAction.Damage;
			hitData.Amount        = damage;
			hitData.InstigatorRef = instigator.Object.InputAuthority;
			hitData.Instigator    = instigator.GetComponent<IHitInstigator>();
			hitData.Position      = collider.transform.position;
			hitData.Normal        = (instigator.transform.position - collider.transform.position).normalized;
			hitData.Direction     = -hitData.Normal;
			hitData.Target        = target;
			hitData.HitType       = hitType;

			return ProcessHit(ref hitData);
		}

		public static HitData ProcessHit(ref HitData hitData)
		{
			hitData.Target.ProcessHit(ref hitData);

			// For local debug targets we show hit feedback immediately
			// if (hitData.Instigator != null && hitData.Target is Health == false)
			// {
			// 	hitData.Instigator.HitPerformed(hitData);
			// }

			return hitData;
		}

		public static IHitTarget GetHitTarget(Hitbox hitbox, Collider collider)
		{
			if (hitbox != null)
				return hitbox.Root.GetComponent<IHitTarget>();

			if (collider == null)
				return null;

			if (ObjectLayerMask.HitTargets.value.IsBitSet(collider.gameObject.layer) == false)
				return null;

			return collider.GetComponentInParent<IHitTarget>();
		}
	}
}