using System;
using System.Linq;
using EntityStates.VoidRaidCrab.Joint;
using EntityStates.VoidRaidCrab.Leg;
using HG;
using UnityEngine;
using UnityEngine.Networking;

namespace RoR2.VoidRaidCrab;

public class LegController : MonoBehaviour
{
	private const string jointDeathStateMachineName = "Body";

	public EntityStateMachine stateMachine;

	public Animator animator;

	public string primaryLayerName;

	[Tooltip("The transform that should be considered the origin of this leg, points outward from the base, and provides a transform for consistent local space conversions. This field must always be set, and is available for the case that the object this component is attached to is not a bone meeting the metioned criteria.")]
	public Transform originTransform;

	[Header("Regeneration")]
	public float jointRegenDuration = 15f;

	[Header("Footstep Concussive Blast")]
	public float footstepBlastRadius = 20f;

	public BlastAttack.FalloffModel footstepFalloffModel = BlastAttack.FalloffModel.SweetSpot;

	public float footstepBlastForce = 500f;

	public Vector3 footstepBonusForce;

	public GameObject footstepBlastEffectPrefab;

	[Header("Stomp")]
	public Transform stompRangeNearMarker;

	public Transform stompRangeFarMarker;

	public Transform stompRangeLeftMarker;

	public Transform stompRangeRightMarker;

	public float stompAimSpeed = 15f;

	public string stompXParameter;

	public string stompYParameter;

	public string stompPlaybackRateParam;

	private int stompXParameterHash;

	private int stompYParameterHash;

	private int stompPlaybackRateHash;

	[Header("Retraction")]
	public AnimationCurve retractionCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);

	public string retractionLayerName;

	private float currentRetractionWeight;

	private float desiredRetractionWeight;

	public float retractionBlendTransitionRate = 1f;

	public bool shouldRetract;

	[NonSerialized]
	public bool isBreakSuppressed;

	private bool wasJointBodyDead;

	private bool isJointBodyCurrentlyDying;

	private float jointRegenStopwatchServer;

	private Vector3? stompTargetWorldPosition;

	private Vector2 currentLocalStompPosition = Vector2.zero;

	public CharacterBody mainBody { get; private set; }

	public GameObject mainBodyGameObject { get; private set; }

	public bool mainBodyHasEffectiveAuthority => mainBody?.hasEffectiveAuthority ?? false;

	public ChildLocator legChildLocator { get; private set; }

	public ChildLocator childLocator { get; private set; }

	public Transform toeTransform { get; private set; }

	public Transform toeTipTransform { get; private set; }

	public Transform footTranform { get; private set; }

	public CharacterMaster jointMaster { get; private set; }

	public CharacterBody jointBody
	{
		get
		{
			if (!jointMaster)
			{
				return null;
			}
			return jointMaster.GetBody();
		}
	}

	public bool IsBusy()
	{
		return !(stateMachine.state is Idle);
	}

	public bool IsStomping()
	{
		return stateMachine.state is BaseStompState;
	}

	private void OnEnable()
	{
		CharacterModel componentInParent = GetComponentInParent<CharacterModel>();
		mainBody = (componentInParent ? componentInParent.body : null);
		mainBodyGameObject = (mainBody ? mainBody.gameObject : null);
		childLocator = GetComponent<ChildLocator>();
		footTranform = childLocator.FindChild("Foot");
		toeTransform = childLocator.FindChild("Toe");
		toeTipTransform = childLocator.FindChild("ToeTip");
		stompXParameterHash = Animator.StringToHash(stompXParameter);
		stompYParameterHash = Animator.StringToHash(stompYParameter);
		stompPlaybackRateHash = Animator.StringToHash(stompPlaybackRateParam);
	}

	private void FixedUpdate()
	{
		desiredRetractionWeight = (shouldRetract ? 1f : 0f);
		currentRetractionWeight = Mathf.Clamp01(Mathf.MoveTowards(currentRetractionWeight, desiredRetractionWeight, Time.fixedDeltaTime * retractionBlendTransitionRate));
		int layerIndex = animator.GetLayerIndex(retractionLayerName);
		float weight = retractionCurve.Evaluate(currentRetractionWeight);
		animator.SetLayerWeight(layerIndex, weight);
		if (!wasJointBodyDead && IsBreakPending())
		{
			wasJointBodyDead = true;
			isJointBodyCurrentlyDying = true;
		}
		if (mainBodyHasEffectiveAuthority)
		{
			UpdateStompTargetPositionAuthority(Time.fixedDeltaTime);
		}
		if (NetworkServer.active && !jointBody)
		{
			jointRegenStopwatchServer += Time.fixedDeltaTime;
			if (jointRegenStopwatchServer >= jointRegenDuration)
			{
				RegenerateServer();
			}
		}
	}

	public bool RequestStomp(GameObject target)
	{
		if (!IsBusy())
		{
			stateMachine.SetNextState(new PreStompLegRaise
			{
				target = target
			});
			return true;
		}
		return false;
	}

	public void SetStompTargetWorldPosition(Vector3? newStompTargetWorldPosition)
	{
		stompTargetWorldPosition = newStompTargetWorldPosition;
	}

	public bool SetJointMaster(CharacterMaster master, ChildLocator legChildLocator)
	{
		this.legChildLocator = legChildLocator;
		if (!jointMaster)
		{
			jointMaster = master;
			if ((bool)jointMaster)
			{
				jointMaster.onBodyDestroyed += OnJointBodyDestroyed;
				jointMaster.onBodyStart += OnJointBodyStart;
				MirrorLegJoints();
			}
			return true;
		}
		Debug.LogError("LegController on " + base.gameObject.name + " already has a jointMaster set!");
		return false;
	}

	private void OnJointBodyStart(CharacterBody body)
	{
		wasJointBodyDead = false;
		isJointBodyCurrentlyDying = false;
		MirrorLegJoints();
	}

	private void OnJointBodyDestroyed(CharacterBody body)
	{
		isJointBodyCurrentlyDying = false;
	}

	public bool IsSupportingWeight()
	{
		if (!IsBroken() && !IsBreakPending())
		{
			return !IsBusy();
		}
		return false;
	}

	public bool CanBreak()
	{
		return jointBody;
	}

	public bool IsBreakPending()
	{
		if ((bool)jointBody)
		{
			HealthComponent healthComponent = jointBody.healthComponent;
			if ((bool)healthComponent)
			{
				return !healthComponent.alive;
			}
			return false;
		}
		return false;
	}

	public bool IsBroken()
	{
		if (!isJointBodyCurrentlyDying)
		{
			return !jointBody;
		}
		return true;
	}

	public bool DoesJointExist()
	{
		return jointBody;
	}

	public void CompleteBreakAuthority()
	{
		if (!jointMaster)
		{
			return;
		}
		CharacterBody characterBody = jointBody;
		if (!characterBody)
		{
			return;
		}
		HealthComponent healthComponent = characterBody.healthComponent;
		if ((bool)healthComponent)
		{
			if (NetworkServer.active)
			{
				DamageInfo damageInfo = new DamageInfo();
				damageInfo.crit = false;
				damageInfo.damage = healthComponent.fullCombinedHealth;
				damageInfo.procCoefficient = 0f;
				mainBody.healthComponent.TakeDamage(damageInfo);
				GlobalEventManager.instance.OnHitEnemy(damageInfo, healthComponent.gameObject);
				GlobalEventManager.instance.OnHitAll(damageInfo, healthComponent.gameObject);
			}
			mainBody.healthComponent.UpdateLastHitTime(0f, Vector3.zero, damageIsSilent: true, healthComponent.lastHitAttacker);
		}
		if (EntityStateMachine.FindByCustomName(characterBody.gameObject, "Body").state is PreDeathState preDeathState)
		{
			preDeathState.canProceed = true;
		}
	}

	public void RegenerateServer()
	{
		if ((bool)jointMaster)
		{
			jointRegenStopwatchServer = 0f;
			if (!jointMaster.GetBody())
			{
				jointMaster.Respawn(mainBody.transform.position, mainBody.transform.rotation);
			}
		}
	}

	private void MirrorLegJoints()
	{
		GameObject bodyObject = jointMaster.GetBodyObject();
		if ((bool)bodyObject && (bool)legChildLocator)
		{
			ChildLocatorMirrorController component = bodyObject.GetComponent<ChildLocatorMirrorController>();
			if ((bool)component)
			{
				component.referenceLocator = legChildLocator;
			}
		}
	}

	private Vector2 WorldPointToLocalStompPoint(Vector3 worldPoint)
	{
		Vector3 vector = originTransform.InverseTransformVector(worldPoint);
		return new Vector2(vector.x, vector.z);
	}

	private Vector2 LocalStompPointToStompParams(Vector2 stompPoint)
	{
		float x = originTransform.InverseTransformVector(stompRangeLeftMarker.position).x;
		float x2 = originTransform.InverseTransformVector(stompRangeRightMarker.position).x;
		float z = originTransform.InverseTransformVector(stompRangeNearMarker.position).z;
		float z2 = originTransform.InverseTransformVector(stompRangeFarMarker.position).z;
		float x3 = Util.Remap(currentLocalStompPosition.x, x, x2, -1f, 1f);
		float y = Util.Remap(currentLocalStompPosition.y, z, z2, -1f, 1f);
		return new Vector2(x3, y);
	}

	private void UpdateStompTargetPositionAuthority(float deltaTime)
	{
		Vector3 worldPoint = toeTipTransform.position;
		if (stompTargetWorldPosition.HasValue)
		{
			worldPoint = stompTargetWorldPosition.Value;
		}
		Vector2 target = WorldPointToLocalStompPoint(worldPoint);
		currentLocalStompPosition = Vector2.MoveTowards(currentLocalStompPosition, target, stompAimSpeed * deltaTime);
		Vector2 vector = LocalStompPointToStompParams(currentLocalStompPosition);
		animator.SetFloat(stompXParameterHash, vector.x);
		animator.SetFloat(stompYParameterHash, vector.y);
	}

	private bool GetKneeToToeTipRaycast(out Vector3 hitPosition, out Vector3 hitNormal, out Vector3 rayNormal)
	{
		Vector3 position = footTranform.position;
		Vector3 position2 = toeTipTransform.position;
		Vector3 vector = position2 - position;
		float magnitude = vector.magnitude;
		if (magnitude <= Mathf.Epsilon)
		{
			hitPosition = position2;
			hitNormal = Vector3.up;
			rayNormal = Vector3.down;
			return false;
		}
		Vector3 vector2 = vector / magnitude;
		RaycastHit[] array = Physics.RaycastAll(new Ray(position, vector2), magnitude, LayerIndex.world.mask, QueryTriggerInteraction.Ignore);
		float num = float.PositiveInfinity;
		int num2 = -1;
		for (int i = 0; i < array.Length; i++)
		{
			ref RaycastHit reference = ref array[i];
			if (reference.distance < num && !(reference.collider.transform.root == base.transform.root))
			{
				num2 = i;
				num = reference.distance;
			}
		}
		rayNormal = vector2;
		if (num2 != -1)
		{
			ref RaycastHit reference2 = ref array[num2];
			hitPosition = reference2.point;
			hitNormal = reference2.normal;
			return true;
		}
		hitPosition = toeTipTransform.position;
		hitNormal = -rayNormal;
		return false;
	}

	public void DoToeConcussionBlastAuthority(Vector3? positionOverride = null, bool useEffect = true)
	{
		if (!mainBodyHasEffectiveAuthority)
		{
			throw new Exception("Caller does not have authority.");
		}
		Vector3 hitPosition;
		if (positionOverride.HasValue)
		{
			hitPosition = positionOverride.Value;
		}
		else
		{
			GetKneeToToeTipRaycast(out hitPosition, out var _, out var _);
		}
		if (useEffect)
		{
			EffectData effectData = new EffectData();
			effectData.origin = hitPosition;
			effectData.scale = footstepBlastRadius;
			effectData.rotation = Quaternion.identity;
			EffectManager.SpawnEffect(footstepBlastEffectPrefab, effectData, transmit: true);
		}
		BlastAttack blastAttack = new BlastAttack();
		blastAttack.attacker = mainBodyGameObject;
		blastAttack.teamIndex = (mainBody ? mainBody.teamComponent.teamIndex : TeamIndex.None);
		blastAttack.attackerFiltering = AttackerFiltering.NeverHitSelf;
		blastAttack.inflictor = mainBodyGameObject;
		blastAttack.radius = footstepBlastRadius;
		blastAttack.position = hitPosition;
		blastAttack.losType = BlastAttack.LoSType.None;
		blastAttack.procCoefficient = 0f;
		blastAttack.procChainMask = default(ProcChainMask);
		blastAttack.baseDamage = 0f;
		blastAttack.baseForce = footstepBlastForce;
		blastAttack.bonusForce = footstepBonusForce;
		blastAttack.canRejectForce = false;
		blastAttack.crit = false;
		blastAttack.damageColorIndex = DamageColorIndex.Default;
		blastAttack.damageType = DamageType.Silent;
		blastAttack.impactEffect = EffectIndex.Invalid;
		blastAttack.falloffModel = footstepFalloffModel;
		blastAttack.Fire();
	}

	public GameObject CheckForStompTarget()
	{
		Vector3 a2 = stompRangeLeftMarker.position;
		Vector3 b2 = stompRangeRightMarker.position;
		Vector3 c2 = stompRangeNearMarker.position;
		Vector3 d2 = stompRangeFarMarker.position;
		Vector3 vector = Average(in a2, in b2, in c2, in d2);
		float a3 = Vector3.Distance(vector, a2);
		float b3 = Vector3.Distance(vector, b2);
		float c3 = Vector3.Distance(vector, c2);
		float d3 = Vector3.Distance(vector, d2);
		float num = Min(a3, b3, c3, d3);
		float num2 = Mathf.Sqrt(2f);
		float num3 = 1f / num2;
		float maxDistanceFilter = num * num3;
		BullseyeSearch bullseyeSearch = new BullseyeSearch();
		bullseyeSearch.searchOrigin = vector;
		bullseyeSearch.minDistanceFilter = 0f;
		bullseyeSearch.maxDistanceFilter = maxDistanceFilter;
		bullseyeSearch.sortMode = BullseyeSearch.SortMode.Distance;
		bullseyeSearch.viewer = mainBody;
		bullseyeSearch.teamMaskFilter = TeamMask.AllExcept(mainBody.teamComponent.teamIndex);
		bullseyeSearch.filterByDistinctEntity = true;
		bullseyeSearch.filterByLoS = false;
		bullseyeSearch.RefreshCandidates();
		return bullseyeSearch.GetResults().FirstOrDefault()?.healthComponent?.gameObject;
		static Vector3 Average(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d)
		{
			Vector3 a4 = Vector3Utils.Average(in a, in b);
			Vector3 b4 = Vector3Utils.Average(in c, in d);
			return Vector3Utils.Average(in a4, in b4);
		}
		static float Min(float a, float b, float c, float d)
		{
			return Mathf.Min(Mathf.Min(a, b), Mathf.Min(c, d));
		}
	}
}
