using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.Serialization;

namespace RoR2.Projectile;

[RequireComponent(typeof(ProjectileTargetComponent))]
[RequireComponent(typeof(TeamFilter))]
public class ProjectileDirectionalTargetFinder : MonoBehaviour
{
	[Tooltip("How far ahead the projectile should look to find a target.")]
	public float lookRange;

	[Range(0f, 180f)]
	[Tooltip("How wide the cone of vision for this projectile is in degrees. Limit is 180.")]
	public float lookCone;

	[Tooltip("How long before searching for a target.")]
	public float targetSearchInterval = 0.5f;

	[Tooltip("Will not search for new targets once it has one.")]
	public bool onlySearchIfNoTarget;

	[Tooltip("Allows the target to be lost if it's outside the acceptable range.")]
	public bool allowTargetLoss;

	[Tooltip("If set, targets can only be found when there is a free line of sight.")]
	public bool testLoS;

	[Tooltip("Whether or not airborne characters should be ignored.")]
	public bool ignoreAir;

	[Tooltip("The difference in altitude at which a result will be ignored.")]
	[FormerlySerializedAs("altitudeTolerance")]
	public float flierAltitudeTolerance = float.PositiveInfinity;

	public UnityEvent onNewTargetFound;

	[FormerlySerializedAs("ontargetLost")]
	public UnityEvent onTargetLost;

	private new Transform transform;

	private TeamFilter teamFilter;

	private ProjectileTargetComponent targetComponent;

	private float searchTimer;

	private bool hasTarget;

	private bool hadTargetLastUpdate;

	private BullseyeSearch bullseyeSearch;

	private HurtBox lastFoundHurtBox;

	private Transform lastFoundTransform;

	private void Start()
	{
		if (!NetworkServer.active)
		{
			base.enabled = false;
			return;
		}
		bullseyeSearch = new BullseyeSearch();
		teamFilter = GetComponent<TeamFilter>();
		targetComponent = GetComponent<ProjectileTargetComponent>();
		transform = base.transform;
		searchTimer = 0f;
	}

	private void FixedUpdate()
	{
		searchTimer -= Time.fixedDeltaTime;
		if (!(searchTimer <= 0f))
		{
			return;
		}
		searchTimer += targetSearchInterval;
		if (allowTargetLoss && (object)targetComponent.target != null && (object)lastFoundTransform == targetComponent.target && !PassesFilters(lastFoundHurtBox))
		{
			SetTarget(null);
		}
		if (!onlySearchIfNoTarget || targetComponent.target == null)
		{
			SearchForTarget();
		}
		hasTarget = targetComponent.target != null;
		if (hadTargetLastUpdate != hasTarget)
		{
			if (hasTarget)
			{
				onNewTargetFound?.Invoke();
			}
			else
			{
				onTargetLost?.Invoke();
			}
		}
		hadTargetLastUpdate = hasTarget;
	}

	private bool PassesFilters(HurtBox result)
	{
		CharacterBody body = result.healthComponent.body;
		if (!body || (ignoreAir && body.isFlying))
		{
			return false;
		}
		if (body.isFlying && !float.IsInfinity(flierAltitudeTolerance) && flierAltitudeTolerance < Mathf.Abs(result.transform.position.y - transform.position.y))
		{
			return false;
		}
		return true;
	}

	private void SearchForTarget()
	{
		bullseyeSearch.teamMaskFilter = TeamMask.allButNeutral;
		bullseyeSearch.teamMaskFilter.RemoveTeam(teamFilter.teamIndex);
		bullseyeSearch.filterByLoS = testLoS;
		bullseyeSearch.searchOrigin = transform.position;
		bullseyeSearch.searchDirection = transform.forward;
		bullseyeSearch.maxDistanceFilter = lookRange;
		bullseyeSearch.sortMode = BullseyeSearch.SortMode.DistanceAndAngle;
		bullseyeSearch.maxAngleFilter = lookCone;
		bullseyeSearch.RefreshCandidates();
		IEnumerable<HurtBox> source = bullseyeSearch.GetResults().Where(PassesFilters);
		SetTarget(source.FirstOrDefault());
	}

	private void SetTarget(HurtBox hurtBox)
	{
		lastFoundHurtBox = hurtBox;
		lastFoundTransform = hurtBox?.transform;
		targetComponent.target = lastFoundTransform;
	}

	private void OnDrawGizmosSelected()
	{
		Gizmos.color = Color.yellow;
		Transform transform = base.transform;
		Vector3 position = transform.position;
		Gizmos.DrawWireSphere(position, lookRange);
		Gizmos.DrawRay(position, transform.forward * lookRange);
		Gizmos.DrawFrustum(position, lookCone * 2f, lookRange, 0f, 1f);
		if (!float.IsInfinity(flierAltitudeTolerance))
		{
			Gizmos.DrawWireCube(position, new Vector3(lookRange * 2f, flierAltitudeTolerance * 2f, lookRange * 2f));
		}
	}
}
