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

namespace Autohand
{
    [RequireComponent(typeof(Grabbable)), DefaultExecutionOrder(10)]
    public class GrabbableHeldJoint : MonoBehaviour
    {
        [Tooltip("The grabbable that this grabbable is connected to")]
        public Grabbable connectedGrabbable;
        [Tooltip("Offsets the center of this joint")]
        public Vector3 pivotOffset;
        [Tooltip("This will multiply the hands strength while holding this grabbable to give it more or less positional priority while holding this joint as a second hand (good to reduce when you dont want this joint having movement priority while being held)")]
        [Min(0.1f)]
        public float heldMassScale = 1f;
        [Space]
        [Tooltip("The maximum distance this joint is allowed to move in the local positive x-axis")]
        public float xMaxLimit = 0f;
        [Tooltip("The maximum distance this joint is allowed to move in the local negative x-axis")]
        public float xMinLimit = 0f;
        [Tooltip("This will force the joint back into its center position based on the given spring strength while not being held along this axis")]
        public float xSpring = 0f;
        [Space]
        [Tooltip("The maximum distance this joint is allowed to move in the local positive y-axis")]
        public float yMaxLimit = 0f;
        [Tooltip("The maximum distance this joint is allowed to move in the local negative y-axis")]
        public float yMinLimit = 0f;
        [Tooltip("This will force the joint back into its center position based on the given spring strength while not being held along this axis")]
        public float ySpring = 0f;
        [Space]
        [Tooltip("The maximum distance this joint is allowed to move in the local positive z-axis")]
        public float zMaxLimit = 0f;
        [Tooltip("The maximum distance this joint is allowed to move in the local negative z-axis")]
        public float zMinLimit = 0f;
        [Tooltip("This will force the joint back into its center position based on the given spring strength while not being held along this axis")]
        public float zSpring = 0f;

        [Space]
        [Range(0, 1), Tooltip("The percentage from the min/max distance needed to trigger the event, good for creating a buffer for the event to trigger slightly before the max range to help prevent missed event")]
        public float eventOffset = 0.05f;

        public UnityHandGrabEvent OnMinDistanceEvent;
        public UnityHandGrabEvent OnMaxDistanceEvent;

        bool triggeredMaxEvent;
        bool triggeredMinEvent;

        Vector3 handLocalPosition;
        Vector3 localPosition;
        Vector3 localTargetPosition;
        Vector3 localStartPosition;

        Grabbable grabbable;
        Vector3 localOrigin;
        Vector3 localStartOrigin;
        Hand jointHand;
        bool grabFrame;
        bool started = false;
        public void Start()
        {
            if(started)
                return;

            ResetOrigin();

            if(grabbable == null) {
                grabbable = GetComponent<Grabbable>();
                grabbable.OnGrabEvent += OnGrabbed;
                grabbable.OnReleaseEvent += OnReleased;
            }
            grabbable.singleHandOnly = true;

            if(xMinLimit == 0 && yMinLimit == 0 && zMinLimit == 0)
                triggeredMinEvent = true;
            started = true;
        }

        public void ResetOrigin()
        {
            localOrigin = connectedGrabbable.rootTransform.InverseTransformPoint(transform.position) + pivotOffset;
            localStartOrigin = localOrigin;
        }

        internal void OnGrabbed(Hand hand, Grabbable grab)
        {
            jointHand = hand;
            hand.body.mass *= heldMassScale;

            localOrigin = hand.heldJoint.connectedAnchor + pivotOffset;
            localTargetPosition = new Vector3(
                Mathf.Clamp(handLocalPosition.x, localOrigin.x + xMinLimit, localOrigin.x + xMaxLimit),
                Mathf.Clamp(handLocalPosition.y, localOrigin.y + yMinLimit, localOrigin.y + yMaxLimit),
                Mathf.Clamp(handLocalPosition.z, localOrigin.z + zMinLimit, localOrigin.z + zMaxLimit)
            );

            localTargetPosition += pivotOffset;
            grabFrame = true;
        }

        void OnReleased(Hand hand, Grabbable grab)
        {
            jointHand = null;
            hand.body.mass /= heldMassScale;

            localOrigin = localStartOrigin;
        }

        private void FixedUpdate()
        {
            if(grabFrame) {
                grabFrame = false;
                return;
            }

            UpdateJoint();
        }

        public void SetJointMax() {
            transform.position = connectedGrabbable.rootTransform.TransformPoint(localOrigin + pivotOffset + new Vector3(xMaxLimit, yMaxLimit, zMaxLimit));
        }

        public void SetJointMin() {
            transform.position = connectedGrabbable.rootTransform.TransformPoint(localOrigin + pivotOffset + new Vector3(xMinLimit, yMinLimit, zMinLimit));
        }

        public void UpdateJoint()
        {
            if (connectedGrabbable.HeldCount() > 0 && grabbable.HeldCount(false, false, true) > 0 && jointHand != null)
            {
                handLocalPosition = jointHand.heldJoint.connectedBody.transform.InverseTransformPoint(jointHand.moveTo.position) + pivotOffset;
                localPosition = jointHand.heldJoint.connectedAnchor + pivotOffset;
                localTargetPosition = new Vector3(
                    Mathf.MoveTowards(localPosition.x, Mathf.Clamp(handLocalPosition.x, localOrigin.x + xMinLimit, localOrigin.x + xMaxLimit), 1f),
                    Mathf.MoveTowards(localPosition.y, Mathf.Clamp(handLocalPosition.y, localOrigin.y + yMinLimit, localOrigin.y + yMaxLimit), 1f),
                    Mathf.MoveTowards(localPosition.z, Mathf.Clamp(handLocalPosition.z, localOrigin.z + zMinLimit, localOrigin.z + zMaxLimit), 1f)
                );

                if(localPosition != localTargetPosition) {
                    //Vector3 globalTargetPosition = connectedGrabbable.rootTransform.TransformPoint(localTargetPosition - jointHand.handGrabPoint.localPosition);
                    transform.localPosition += (localTargetPosition - localPosition); // Adjusting position with pivot offset
                    transform.localPosition = new Vector3(
                        Mathf.Clamp(transform.localPosition.x, localStartOrigin.x + xMinLimit, localStartOrigin.x + xMaxLimit),
                        Mathf.Clamp(transform.localPosition.y, localStartOrigin.y + yMinLimit, localStartOrigin.y + yMaxLimit),
                        Mathf.Clamp(transform.localPosition.z, localStartOrigin.z + zMinLimit, localStartOrigin.z + zMaxLimit)
                    );
                    jointHand.heldJoint.connectedAnchor += (localTargetPosition - localPosition);
                    jointHand.handGrabPoint.localPosition += (localTargetPosition - localPosition);
                }
            }
            else
            {
                Vector3 localPosition = connectedGrabbable.transform.InverseTransformPoint(transform.position) + pivotOffset;
                Vector3 localTargetPosition = new Vector3(
                    Mathf.MoveTowards(localPosition.x, localOrigin.x, Time.fixedDeltaTime * xSpring),
                    Mathf.MoveTowards(localPosition.y, localOrigin.y, Time.fixedDeltaTime * ySpring),
                    Mathf.MoveTowards(localPosition.z, localOrigin.z, Time.fixedDeltaTime * zSpring)
                );

                if(transform.localPosition != localOrigin)
                    transform.position = connectedGrabbable.rootTransform.TransformPoint(localTargetPosition);
            }
                    
            if(OnMaxDistanceEvent != null) {
                var localPos = connectedGrabbable.rootTransform.InverseTransformPoint(transform.position);
                bool greaterOrEqual =
                    localPos.x >= localStartOrigin.x + xMaxLimit - xMaxLimit * eventOffset - 0.001f &&
                    localPos.y >= localStartOrigin.y + yMaxLimit - yMaxLimit * eventOffset - 0.001f &&
                    localPos.z >= localStartOrigin.z + zMaxLimit - zMaxLimit * eventOffset - 0.001f; 

                if(greaterOrEqual && !triggeredMaxEvent) {
                    OnMaxDistanceEvent.Invoke(jointHand, connectedGrabbable);
                    triggeredMaxEvent = true;
                    triggeredMinEvent = false;
                }
            }

            if(OnMinDistanceEvent != null) {
                var localPos = connectedGrabbable.rootTransform.InverseTransformPoint(transform.position);
                bool lessOrEqual =
                    localPos.x <= localStartOrigin.x + xMinLimit - xMinLimit * eventOffset + 0.001f &&
                    localPos.y <= localStartOrigin.y + yMinLimit - yMinLimit * eventOffset + 0.001f &&
                    localPos.z <= localStartOrigin.z + zMinLimit - zMinLimit * eventOffset + 0.001f;

                if(lessOrEqual && !triggeredMinEvent) {
                    OnMinDistanceEvent.Invoke(jointHand, connectedGrabbable);
                    triggeredMinEvent = true;
                    triggeredMaxEvent = false;
                }
            }
        }
    }
}