// Copyright (c) 2026 EzAdmin. All rights reserved.
// Licensed under the EzAdmin Client License. See LICENSE-CLIENT.txt.

using UnityEngine;
using UnityEngine.Events;

namespace EzAdmin.Client
{

    [DisallowMultipleComponent]
    public sealed class EzAdminTouchButton : MonoBehaviour
    {
        [EzAdminGroup("What this button does")]
        [Tooltip("Raised on click, on pointer, and on physical touch alike.")]
        [SerializeField] private UnityEvent _onPressed = new UnityEvent();

        [EzAdminGroup("Physical touch (VR)")]
        [Tooltip("Let a hand or finger collider press this by moving into it.")]
        [SerializeField] private bool _enablePhysicalTouch = true;

        [Tooltip("Which layers count as a hand. Leave as Everything to accept any " +
                 "trigger, but a dedicated hand layer is strongly recommended — " +
                 "otherwise a dropped object rolling into the panel presses it.")]
        [SerializeField] private LayerMask _touchLayers = ~0;

        [Tooltip("Seconds before this button can be pressed again. Stops a hand " +
                 "resting against the collider from firing on every physics step.")]
        [SerializeField, Range(0.05f, 2f)] private float _rearmSeconds = 0.35f;

        [Tooltip("Ignore touches for this long after the button appears, and anything already touching it then. " +
                 "Stops a player spawning into the board from pressing it.")]
        [SerializeField, Range(0f, 10f)] private float _armDelay = 2f;

        [Tooltip("Only something this small (in metres) counts as a hand. A body, a wall or the floor is bigger " +
                 "and never presses it.")]
        [SerializeField, Range(0.05f, 1f)] private float _maxHandSize = 0.3f;

        [EzAdminGroup("Feedback (optional)")]
        [Tooltip("Raised at the same moment as the press — wire a click sound, a " +
                 "haptic pulse, or a button-depress animation here.")]
        [SerializeField] private UnityEvent _onFeedback = new UnityEvent();

        [Tooltip("Raised when a hand enters and leaves, for a highlight effect. " +
                 "Never fires for pointer clicks, only physical touch.")]
        [SerializeField] private UnityEvent _onTouchEnter = new UnityEvent();
        [SerializeField] private UnityEvent _onTouchExit = new UnityEvent();

        private float _nextAllowedTime;
        private bool _isInteractable = true;
        private float _armedAt;
        private readonly System.Collections.Generic.HashSet<Collider> _ignored =
            new System.Collections.Generic.HashSet<Collider>();

        private void OnEnable()
        {
            _armedAt = Time.unscaledTime + _armDelay;
            _ignored.Clear();
        }

        private bool LooksLikeHand(Collider other)
        {
            var size = other.bounds.size;
            return Mathf.Max(size.x, Mathf.Max(size.y, size.z)) <= _maxHandSize;
        }

        public bool IsInteractable
        {
            get { return _isInteractable; }
            set { _isInteractable = value; }
        }

        public LayerMask TouchLayers
        {
            get { return _touchLayers; }
            set { _touchLayers = value; }
        }

        public UnityEvent OnPressed
        {
            get { return _onPressed; }
        }

        public void Press()
        {
            if (!_isInteractable)
            {
                return;
            }

            if (Time.unscaledTime < _nextAllowedTime)
            {
                return;
            }

            _nextAllowedTime = Time.unscaledTime + _rearmSeconds;

            _onFeedback.Invoke();
            _onPressed.Invoke();
        }

        private void OnTriggerEnter(Collider other)
        {
            if (!_enablePhysicalTouch || !_isInteractable)
            {
                return;
            }

            if (!IsInTouchLayers(other.gameObject.layer))
            {
                return;
            }

            if (Time.unscaledTime < _armedAt)
            {
                _ignored.Add(other);
                return;
            }

            if (_ignored.Contains(other) || !LooksLikeHand(other))
            {
                return;
            }

            _onTouchEnter.Invoke();
            Press();
        }

        private void OnTriggerExit(Collider other)
        {
            if (!_enablePhysicalTouch)
            {
                return;
            }

            if (!IsInTouchLayers(other.gameObject.layer))
            {
                return;
            }

            if (_ignored.Remove(other))
            {
                return;
            }

            _onTouchExit.Invoke();
        }

        private bool IsInTouchLayers(int layer)
        {
            return (_touchLayers.value & (1 << layer)) != 0;
        }

#if UNITY_EDITOR
        private void OnValidate()
        {
            if (!_enablePhysicalTouch || gameObject.hideFlags != HideFlags.None || !gameObject.scene.IsValid())
            {
                return;
            }

            var ownCollider = GetComponent<Collider>();
            if (ownCollider == null)
            {
                Debug.LogWarning(
                    "EzAdminTouchButton on '" + name + "' has Physical Touch enabled but " +
                    "no Collider. Add one and tick Is Trigger.", this);
            }
            else if (!ownCollider.isTrigger)
            {
                Debug.LogWarning(
                    "EzAdminTouchButton on '" + name + "': the Collider should be a " +
                    "trigger, or a hand will bump into it instead of pressing it.", this);
            }
        }
#endif
    }
}
