using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.EnhancedTouch;
using UnityEngine.InputSystem.Utilities;
using EnhancedTouchSupport = UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport;
using EnhancedTouchTouch = UnityEngine.InputSystem.EnhancedTouch.Touch;
using EnhancedTouchPhase = UnityEngine.InputSystem.TouchPhase;
using UnityTouch = UnityEngine.Touch;
using TouchPhaseLegacy = UnityEngine.TouchPhase;

/// <summary>
/// Aggregates look input from mouse, gamepad sticks, and mobile touch into a single delta value.
/// Works with either the legacy Input Manager or the newer Input System.
/// </summary>
[DisallowMultipleComponent]
public sealed class LookInputRouter : MonoBehaviour
{
    private const float TinyThreshold = 0.0001f;

    [Header("General")]
    [SerializeField] private bool autoDetectInputSystem = true;
    [SerializeField] private bool preferNewInputSystem = true;

    [Header("Input System (New)")]
    [Tooltip("Optional PlayerInput component containing the action map with a Look action.")]
    [SerializeField] private PlayerInput playerInput;

    [Tooltip("Action name used for look deltas when using the new Input System.")]
    [SerializeField] private string lookActionName = "Look";

    [Header("Legacy Input Manager")]
    [SerializeField] private string mouseXAxis = "Mouse X";
    [SerializeField] private string mouseYAxis = "Mouse Y";
    [SerializeField] private string rightStickXAxis = "Joystick X";
    [SerializeField] private string rightStickYAxis = "Joystick Y";

    [Header("Sensitivities")]
    [SerializeField] private float mouseSensitivity = 1.0f;
    [SerializeField] private float stickSensitivity = 120.0f;
    [SerializeField] private float touchSensitivity = 0.15f;

    [Header("Filtering")]
    [Tooltip("Inputs below this magnitude will be ignored and reset to zero.")]
    [SerializeField] private float deadzone = 0.05f;

    [Tooltip("Optional smoothing duration in seconds. Set to zero for raw input.")]
    [SerializeField] private float smoothingTime = 0.08f;

    [Tooltip("Maximum magnitude allowed for a single look delta.")]
    [SerializeField] private float maxMagnitude = 5.0f;

    private InputAction cachedLookAction;
    private bool usingNewInputSystem;

    private Vector2 currentDelta;
    private Vector2 smoothVelocity;

    // Touch state
    private int activeTouchId = -1;
    private Vector2 lastTouchPosition;

        private void Awake()
        {
            DetectInputSystem();
        }

        private void OnEnable()
        {
            // Enable Enhanced Touch so we can use the high-level Touch struct API.
            EnhancedTouchSupport.Enable();

            if (usingNewInputSystem)
            {
                EnableLookAction();
            }
        }

        private void OnDisable()
        {
            if (cachedLookAction != null)
            {
                cachedLookAction.Disable();
            }
            cachedLookAction = null;

            // Pair with Enable() to keep the global reference count balanced.
            EnhancedTouchSupport.Disable();
        }

        private void Update()
        {
            Vector2 rawDelta = usingNewInputSystem ? ReadNewSystemDelta() : ReadLegacyDelta();

            if (!usingNewInputSystem && preferNewInputSystem)
            {
                // If no PlayerInput was supplied but the Input System package is present,
                // fall back to gathering from Gamepad/Touchscreen directly.
                rawDelta += ReadDirectDeviceDelta();
            }

            rawDelta = Vector2.ClampMagnitude(rawDelta, maxMagnitude);

            if (smoothingTime > TinyThreshold)
            {
                currentDelta = Vector2.SmoothDamp(currentDelta, rawDelta, ref smoothVelocity, smoothingTime, Mathf.Infinity, Time.unscaledDeltaTime);
            }
            else
            {
                currentDelta = rawDelta;
            }

            if (currentDelta.magnitude < deadzone)
            {
                currentDelta = Vector2.zero;
            }
        }

        public bool TryGetLookDelta(out Vector2 delta)
        {
            delta = currentDelta;
            return delta.sqrMagnitude > TinyThreshold;
        }

        private void DetectInputSystem()
        {
            if (!autoDetectInputSystem)
            {
                usingNewInputSystem = preferNewInputSystem && playerInput != null;
                CacheLookAction();
                return;
            }

            // If PlayerInput is explicitly assigned, use it.
            if (playerInput == null)
            {
                playerInput = GetComponent<PlayerInput>();
            }

            if (playerInput != null && preferNewInputSystem)
            {
                usingNewInputSystem = true;
                CacheLookAction();
            }
            else
            {
                usingNewInputSystem = false;
            }
        }

        private void CacheLookAction()
        {
            if (playerInput == null)
                return;

            var actions = playerInput.actions;
            if (actions == null)
                return;

            cachedLookAction = actions.FindAction(lookActionName, false);
            if (cachedLookAction != null && !cachedLookAction.enabled && isActiveAndEnabled)
            {
                cachedLookAction.SafeEnable();
            }
        }

        private void EnableLookAction()
        {
            if (cachedLookAction == null && playerInput != null)
            {
                CacheLookAction();
            }

                cachedLookAction?.SafeEnable();
        }

        private Vector2 ReadNewSystemDelta()
        {
            if (cachedLookAction == null)
                return ReadDirectDeviceDelta();

            Vector2 value = cachedLookAction.ReadValue<Vector2>();
            return value * Time.unscaledDeltaTime;
        }

        private Vector2 ReadLegacyDelta()
        {
            // Replace legacy Input.GetAxis usage with InputSystem device reads where possible.
            float x = 0f, y = 0f;

            if (UnityEngine.InputSystem.Mouse.current != null)
            {
                Vector2 mouseDelta = UnityEngine.InputSystem.Mouse.current.delta.ReadValue();
                x = mouseDelta.x * mouseSensitivity;
                y = mouseDelta.y * mouseSensitivity;
            }

            float stickX = 0f, stickY = 0f;
            if (UnityEngine.InputSystem.Gamepad.current != null)
            {
                Vector2 right = UnityEngine.InputSystem.Gamepad.current.rightStick.ReadValue();
                stickX = right.x * stickSensitivity * Time.unscaledDeltaTime;
                stickY = right.y * stickSensitivity * Time.unscaledDeltaTime;
            }

            return new Vector2(x + stickX, y + stickY);
        }

        private Vector2 ReadDirectDeviceDelta()
        {
            Vector2 aggregate = Vector2.zero;

            if (Mouse.current != null)
            {
                Vector2 mouseDelta = Mouse.current.delta.ReadValue();
                aggregate += mouseDelta * mouseSensitivity * Time.unscaledDeltaTime;
            }

            if (Gamepad.current != null)
            {
                Vector2 stick = Gamepad.current.rightStick.ReadValue();
                aggregate += stick * stickSensitivity * Time.unscaledDeltaTime;
            }

            aggregate += ReadTouchDelta();

            return aggregate;
        }

        private Vector2 ReadTouchDelta()
        {
            if (EnhancedTouchSupport.enabled)
            {
                var enhancedDelta = ReadEnhancedTouchDelta();
                if (enhancedDelta != Vector2.zero || EnhancedTouchTouch.activeTouches.Count > 0)
                    return enhancedDelta;
            }

            if (Touchscreen.current != null)
            {
                var touchscreenDelta = ReadTouchscreenDelta();
                if (touchscreenDelta != Vector2.zero)
                    return touchscreenDelta;
            }

            return ReadLegacyTouch();
        }

        private Vector2 ReadEnhancedTouchDelta()
        {
            var touches = EnhancedTouchTouch.activeTouches;
            if (touches.Count <= 0)
            {
                ResetTouchTracking();
                return Vector2.zero;
            }

            for (int i = 0; i < touches.Count; ++i)
            {
                var touch = touches[i];
                if (!touch.valid)
                    continue;

                if (activeTouchId == -1)
                {
                    activeTouchId = touch.touchId;
                    lastTouchPosition = touch.screenPosition;
                }

                if (touch.touchId != activeTouchId)
                    continue;

                if (touch.phase == EnhancedTouchPhase.Ended || touch.phase == EnhancedTouchPhase.Canceled)
                {
                    ResetTouchTracking();
                    return Vector2.zero;
                }

                if (touch.phase == EnhancedTouchPhase.Began)
                {
                    lastTouchPosition = touch.screenPosition;
                    return Vector2.zero;
                }

                Vector2 delta = (touch.screenPosition - lastTouchPosition) * touchSensitivity * Time.unscaledDeltaTime;
                lastTouchPosition = touch.screenPosition;
                return delta;
            }

            return Vector2.zero;
        }

        private Vector2 ReadTouchscreenDelta()
        {
            if (Touchscreen.current == null)
            {
                return Vector2.zero;
            }

            if (!AnyTouchPressed(Touchscreen.current.touches))
            {
                ResetTouchTracking();
                return Vector2.zero;
            }

            foreach (var touchControl in Touchscreen.current.touches)
            {
                if (!touchControl.press.isPressed)
                    continue;

                int id = touchControl.touchId.ReadValue();
                Vector2 position = touchControl.position.ReadValue();

                if (activeTouchId == -1)
                {
                    activeTouchId = id;
                    lastTouchPosition = position;
                    continue;
                }

                if (activeTouchId != id)
                    continue;

                Vector2 delta = (position - lastTouchPosition) * touchSensitivity;
                lastTouchPosition = position;
                return delta * Time.unscaledDeltaTime;
            }

            return Vector2.zero;
        }

        private Vector2 ReadLegacyTouch()
        {
            // Prefer the Input System touchscreen if available. Fall back to legacy Input.touches only when necessary.
            if (UnityEngine.InputSystem.Touchscreen.current != null)
            {
                var touches = UnityEngine.InputSystem.Touchscreen.current.touches;
                if (touches.Count <= 0)
                {
                    ResetTouchTracking();
                    return Vector2.zero;
                }

                for (int i = 0; i < touches.Count; ++i)
                {
                    var tc = touches[i];
                    if (!tc.press.isPressed)
                        continue;

                    int id = tc.touchId.ReadValue();
                    Vector2 pos = tc.position.ReadValue();

                    // We already check tc.press.isPressed above. No additional phase check is required
                    // here for the Input System touch path. This avoids ambiguous TouchPhase enum
                    // comparisons between the legacy and new Input System types.

                    if (activeTouchId == -1)
                    {
                        activeTouchId = id;
                        lastTouchPosition = pos;
                        continue;
                    }

                    if (activeTouchId != id)
                        continue;

                    Vector2 delta = (pos - lastTouchPosition) * touchSensitivity * Time.unscaledDeltaTime;
                    lastTouchPosition = pos;
                    return delta;
                }

                return Vector2.zero;
            }

            // Legacy fallback
            if (Input.touchCount <= 0)
            {
                ResetTouchTracking();
                return Vector2.zero;
            }

            for (int i = 0; i < Input.touchCount; i++)
            {
                UnityTouch touch = Input.GetTouch(i);
                if (touch.phase == TouchPhaseLegacy.Ended || touch.phase == TouchPhaseLegacy.Canceled)
                    continue;

                if (activeTouchId == -1)
                {
                    activeTouchId = touch.fingerId;
                    lastTouchPosition = touch.position;
                    continue;
                }

                if (activeTouchId != touch.fingerId)
                    continue;

                Vector2 delta = touch.deltaPosition * touchSensitivity * Time.unscaledDeltaTime;
                lastTouchPosition = touch.position;
                return delta;
            }

            return Vector2.zero;
        }

        private void ResetTouchTracking()
        {
            activeTouchId = -1;
            lastTouchPosition = Vector2.zero;
        }

        public void ResetLookDelta()
        {
            currentDelta = Vector2.zero;
            smoothVelocity = Vector2.zero;
        }
    private static bool AnyTouchPressed(ReadOnlyArray<TouchControl> touches)
    {
        for (int i = 0; i < touches.Count; ++i)
        {
            if (touches[i].press.isPressed)
                return true;
        }

        return false;
    }
}
