using ABI_RC.Core.InteractionSystem;
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.InputManagement;
using UnityEngine;

namespace NAK.IKAdjustments.Systems;

public class IKAdjuster : MonoBehaviour
{
    #region Grab Definitions

    public class GrabState
    {
        public bool handGrabbed;
        public TrackingPoint tracker;
        public Vector3 displayOffset;
        public Quaternion displayOffsetRotation;
        public GrabState otherGrab;
    }

    public enum AdjustMode
    {
        Position = 0,
        Rotation,
        Both
    }

    #endregion

    public static IKAdjuster Instance;

    public bool isAdjustMode;

    public float Setting_MaxGrabDistance = 0.2f;
    public AdjustMode Setting_AdjustMode = AdjustMode.Position;

    private GrabState leftGrabState = new();
    private GrabState rightGrabState = new();

    #region Unity Events

    private void Start()
    {
        Instance = this;

        leftGrabState.otherGrab = rightGrabState;
        rightGrabState.otherGrab = leftGrabState;
    }

    private void Update()
    {
        if (!isAdjustMode) return;
        if (BodySystem.isCalibrating)
        {
            isAdjustMode = false;
            return;
        }

        UpdateGrabbing(true, ref leftGrabState);
        UpdateGrabbing(false, ref rightGrabState);
    }

    #endregion

    #region Public Methods

    public void EnterAdjustMode()
    {
        if (isAdjustMode)
            return;

        isAdjustMode = true;
        IKSystem.Instance.TrackingSystem.SetTrackingPointVisibility(true);
        CVR_MenuManager.Instance.ToggleQuickMenu(false);
        foreach (TrackingPoint tracker in IKSystem.Instance.TrackingSystem.AllTrackingPoints) tracker.ClearLineTarget();
    }

    public void ExitAdjustMode()
    {
        if (!isAdjustMode)
            return;

        isAdjustMode = false;
        IKSystem.Instance.TrackingSystem.SetTrackingPointVisibility(false);
    }

    public void ResetAllOffsets()
    {
        foreach (TrackingPoint tracker in IKSystem.Instance.TrackingSystem.AllTrackingPoints)
        {
            tracker.offsetTransform.SetParent(tracker.displayObject.transform, true);
            tracker.displayObject.transform.localPosition = Vector3.zero;
            tracker.displayObject.transform.localRotation = Quaternion.identity;
            tracker.offsetTransform.SetParent(tracker.referenceTransform, true);
        }
    }

    public void CycleAdjustMode()
    {
        var currentValue = (int)Setting_AdjustMode;
        var numValues = Enum.GetValues(typeof(AdjustMode)).Length;
        var nextValue = (currentValue + 1) % numValues;
        Setting_AdjustMode = (AdjustMode)nextValue;
    }

    #endregion

    #region Private Methods

    private void UpdateGrabbing(bool isLeft, ref GrabState grabState)
    {
        var isGrabbing = isLeft
            ? CVRInputManager.Instance.gripLeftValue > 0.9f
            : CVRInputManager.Instance.gripRightValue > 0.9f;
        var isInteracting = isLeft
            ? CVRInputManager.Instance.interactLeftValue > 0.9f
            : CVRInputManager.Instance.interactRightValue > 0.9f;
        Transform handTracker = isLeft
            ? IKSystem.Instance.leftController.transform
            : IKSystem.Instance.rightController.transform;

        if (grabState.tracker == null && !grabState.handGrabbed && isGrabbing)
        {
            OnGrab(handTracker, grabState);
        }
        else if (grabState.tracker != null)
        {
            if (!isGrabbing)
                OnRelease(grabState);
            else if (isInteracting)
                OnReset(grabState);
            else
                Holding(handTracker, grabState);
        }

        grabState.handGrabbed = isGrabbing;
    }

    private void OnGrab(Transform handTracker, GrabState grabState)
    {
        Transform nearestTransform = FindNearestTransform(handTracker);
        if (nearestTransform != null &&
            Vector3.Distance(nearestTransform.GetChild(0).position, handTracker.position) <=
            Setting_MaxGrabDistance)
        {
            grabState.tracker =
                IKSystem.Instance.TrackingSystem.AllTrackingPoints.Find(tp => tp.referenceTransform == nearestTransform);
            if (grabState.otherGrab.tracker == grabState.tracker) OnRelease(grabState.otherGrab);
            grabState.displayOffset = grabState.tracker.displayObject.transform.position - handTracker.position;
            grabState.displayOffsetRotation = Quaternion.Inverse(handTracker.rotation) *
                                              grabState.tracker.displayObject.transform.rotation;
            grabState.tracker.offsetTransform.SetParent(grabState.tracker.displayObject.transform, true);
        }
    }

    private void OnRelease(GrabState grabState)
    {
        grabState.tracker.offsetTransform.SetParent(grabState.tracker.referenceTransform, true);
        grabState.tracker.ClearLineTarget();
        grabState.tracker = null;
    }

    private void OnReset(GrabState grabState)
    {
        grabState.tracker.displayObject.transform.localRotation = Quaternion.identity;
        grabState.tracker.displayObject.transform.localPosition = Vector3.zero;

        grabState.tracker.offsetTransform.SetParent(grabState.tracker.referenceTransform, true);
        grabState.tracker.ClearLineTarget();
        grabState.tracker = null;
    }

    private void Holding(Transform handTracker, GrabState grabState)
    {
        switch (Setting_AdjustMode)
        {
            case AdjustMode.Position:
                grabState.tracker.displayObject.transform.position = handTracker.position + grabState.displayOffset;
                break;
            case AdjustMode.Rotation:
                grabState.tracker.displayObject.transform.rotation =
                    handTracker.rotation * grabState.displayOffsetRotation;
                break;
            case AdjustMode.Both:
                grabState.tracker.displayObject.transform.rotation =
                    handTracker.rotation * grabState.displayOffsetRotation;
                grabState.tracker.displayObject.transform.position = handTracker.position + grabState.displayOffset;
                break;
        }

        grabState.tracker.SetLineTarget(grabState.tracker.referenceTransform.position);
    }

    private Transform FindNearestTransform(Transform handTransform)
    {
        var validTrackingPointTransforms = TrackingSystem.ValidTrackingPointTransforms;
        if (validTrackingPointTransforms == null || validTrackingPointTransforms.Count == 0) return null;
        return validTrackingPointTransforms
            .OrderBy(t => Vector3.Distance(handTransform.position, t.GetChild(0).position)).FirstOrDefault();
    }

    #endregion
}