namespace NiAkaDev.EzMirror
{
    using UdonSharp;
    using UnityEngine;
    using VRC.Dynamics;
    using VRC.SDKBase;

    /// <summary>
    /// Contact 入力を EzMirror 本体へ中継する軽量プロキシ。
    /// Contact Receiver はこのコンポーネントと同じ GameObject に置く。
    /// </summary>
    [UdonBehaviourSyncMode(BehaviourSyncMode.None)]
    public class EzMirrorInputProxy : UdonSharpBehaviour
    {
        private const float LocalHandFallbackMaxDistance = 0.45f;

        public EzMirror targetMirror;

        private float _lastContactToggleTime = -1000f;

        public void Start()
        {
            ResolveTargetMirror();
        }

        public override void OnContactEnter(ContactEnterInfo contactInfo)
        {
            ResolveTargetMirror();
            if (targetMirror == null || !targetMirror.enableContactInput)
                return;
            if (!IsLocalContact(contactInfo))
                return;

            float cooldown = Mathf.Max(0.05f, targetMirror.contactCooldownSeconds);
            if (Time.time - _lastContactToggleTime < cooldown)
                return;

            _lastContactToggleTime = Time.time;
            targetMirror.SelectNextFromContact(GetContactHandHint(contactInfo));
        }

        private void ResolveTargetMirror()
        {
            if (targetMirror != null)
                return;

            targetMirror = GetComponentInParent<EzMirror>();
        }

        private bool IsLocalContact(ContactEnterInfo contactInfo)
        {
            ContactSenderProxy sender = contactInfo.contactSender;
            if (!sender.isValid)
                return false;

            VRCPlayerApi senderPlayer = sender.player;
            if (senderPlayer != null && senderPlayer.IsValid())
                return senderPlayer.isLocal;

            if (sender.usage != DynamicsUsage.Avatar)
                return false;

            return IsNearLocalTrackedHand(contactInfo.contactPoint)
                || IsNearLocalTrackedHand(sender.position);
        }

        private int GetContactHandHint(ContactEnterInfo contactInfo)
        {
            int tagHint = GetHandHintFromTags(contactInfo.matchingTags);
            if (tagHint != EzMirror.HapticHandBoth)
                return tagHint;

            return GetNearestHandHint(contactInfo.contactPoint);
        }

        private int GetHandHintFromTags(string[] matchingTags)
        {
            if (matchingTags == null)
                return EzMirror.HapticHandBoth;

            for (int i = 0; i < matchingTags.Length; i++)
            {
                int tagHint = GetHandHintFromTag(matchingTags[i]);
                if (tagHint != EzMirror.HapticHandBoth)
                    return tagHint;
            }

            return EzMirror.HapticHandBoth;
        }

        private int GetHandHintFromTag(string tag)
        {
            if (
                tag == EzMirrorContactTags.HandL
                || tag == EzMirrorContactTags.FingerL
                || tag == EzMirrorContactTags.FingerIndexL
                || tag == EzMirrorContactTags.FingerMiddleL
                || tag == EzMirrorContactTags.FingerRingL
                || tag == EzMirrorContactTags.FingerLittleL
                || tag == EzMirrorContactTags.LegacyLeftHand
                || tag == EzMirrorContactTags.LegacyLeftFinger
                || tag == EzMirrorContactTags.LegacyIndexFingerL
            )
            {
                return EzMirror.HapticHandLeft;
            }

            if (
                tag == EzMirrorContactTags.HandR
                || tag == EzMirrorContactTags.FingerR
                || tag == EzMirrorContactTags.FingerIndexR
                || tag == EzMirrorContactTags.FingerMiddleR
                || tag == EzMirrorContactTags.FingerRingR
                || tag == EzMirrorContactTags.FingerLittleR
                || tag == EzMirrorContactTags.LegacyRightHand
                || tag == EzMirrorContactTags.LegacyRightFinger
                || tag == EzMirrorContactTags.LegacyIndexFingerR
            )
            {
                return EzMirror.HapticHandRight;
            }

            return EzMirror.HapticHandBoth;
        }

        private bool IsNearLocalTrackedHand(Vector3 contactPoint)
        {
            VRCPlayerApi localPlayer = Networking.LocalPlayer;
            if (localPlayer == null || !localPlayer.IsValid())
                return false;

            float maxDistanceSqr =
                LocalHandFallbackMaxDistance * LocalHandFallbackMaxDistance;
            VRCPlayerApi.TrackingData leftHand = localPlayer.GetTrackingData(
                VRCPlayerApi.TrackingDataType.LeftHand
            );
            if ((contactPoint - leftHand.position).sqrMagnitude <= maxDistanceSqr)
                return true;

            VRCPlayerApi.TrackingData rightHand = localPlayer.GetTrackingData(
                VRCPlayerApi.TrackingDataType.RightHand
            );
            return (contactPoint - rightHand.position).sqrMagnitude <= maxDistanceSqr;
        }

        private int GetNearestHandHint(Vector3 contactPoint)
        {
            VRCPlayerApi localPlayer = Networking.LocalPlayer;
            if (localPlayer == null || !localPlayer.IsValid())
                return EzMirror.HapticHandBoth;

            VRCPlayerApi.TrackingData leftHand = localPlayer.GetTrackingData(
                VRCPlayerApi.TrackingDataType.LeftHand
            );
            VRCPlayerApi.TrackingData rightHand = localPlayer.GetTrackingData(
                VRCPlayerApi.TrackingDataType.RightHand
            );
            float leftDistance = (contactPoint - leftHand.position).sqrMagnitude;
            float rightDistance = (contactPoint - rightHand.position).sqrMagnitude;
            return leftDistance <= rightDistance
                ? EzMirror.HapticHandLeft
                : EzMirror.HapticHandRight;
        }
    }
}
