using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace eagle {
    /// <summary>
    /// Handles pinch-to-zoom and scroll-wheel zoom for a map displayed in a ScrollRect.
    /// Attach this to the same GameObject as the ScrollRect.
    /// </summary>
    [RequireComponent(typeof(ScrollRect))]
    public class MapPinchZoomHandler : MonoBehaviour, IScrollHandler {
        public float minZoom = 1.0f;
        public float maxZoom = 3.0f;
        public float scrollZoomSpeed = 0.1f;
        public float pinchZoomSpeed = 0.01f;
        public float zoomSmoothTime = 0.1f;  // Time to smooth zoom changes

        private ScrollRect _scrollRect;
        private RectTransform _content;
        private RectTransform _viewport;
        private Canvas _canvas;
        private float _currentZoom = 1.0f;
        private float _targetZoom = 1.0f;
        private float _zoomVelocity = 0f;
        private Vector2 _lastZoomCenter;

        // For coordinate conversion (used by MapController)
        public float CurrentZoom => _currentZoom;
        public Vector2 PanOffset {
            get {
                if (_content == null) return Vector2.zero;
                return _content.anchoredPosition;
            }
        }

        // Pinch tracking
        private bool _isPinching;
        private float _previousPinchDistance;
        private Vector2 _previousPinchCenter;

        void Awake() {
            _scrollRect = GetComponent<ScrollRect>();
            _content = _scrollRect.content;
            _viewport = _scrollRect.viewport != null ? _scrollRect.viewport
                                                     : GetComponent<RectTransform>();
            _canvas = GetComponentInParent<Canvas>();

            // Disable scrolling at zoom level 1 (no panning needed)
            UpdateScrollability();
        }

        void Update() {
            // Handle touch input on iOS
            if (Application.platform == RuntimePlatform.IPhonePlayer) { HandleTouchInput(); }

            // Smoothly interpolate toward target zoom
            if (!Mathf.Approximately(_currentZoom, _targetZoom)) {
                float previousZoom = _currentZoom;
                _currentZoom = Mathf.SmoothDamp(
                        _currentZoom,
                        _targetZoom,
                        ref _zoomVelocity,
                        zoomSmoothTime);

                // Apply the smoothed zoom
                ApplyCurrentZoom(_lastZoomCenter, previousZoom);
            }
        }

        private void HandleTouchInput() {
            if (Input.touchCount == 2) {
                Touch touch0 = Input.GetTouch(0);
                Touch touch1 = Input.GetTouch(1);

                float currentDistance = Vector2.Distance(touch0.position, touch1.position);
                Vector2 currentCenter = (touch0.position + touch1.position) / 2f;

                if (!_isPinching) {
                    _isPinching = true;
                    _previousPinchDistance = currentDistance;
                    _previousPinchCenter = currentCenter;
                    // Disable scroll rect during pinch to prevent fighting
                    _scrollRect.enabled = false;
                    return;
                }

                // Calculate zoom delta from pinch
                float distanceDelta = currentDistance - _previousPinchDistance;
                float zoomDelta = distanceDelta * pinchZoomSpeed;
                ApplyZoom(zoomDelta, currentCenter);

                _previousPinchDistance = currentDistance;
                _previousPinchCenter = currentCenter;
            } else {
                if (_isPinching) {
                    _isPinching = false;
                    UpdateScrollability();
                }
            }
        }

        // IScrollHandler - captures scroll wheel events
        public void OnScroll(PointerEventData eventData) {
            float scrollDelta = eventData.scrollDelta.y;
            if (scrollDelta != 0) { ApplyZoom(scrollDelta * scrollZoomSpeed, eventData.position); }
        }

        private void ApplyZoom(float delta, Vector2 screenCenter) {
            // Set target zoom - actual zoom is applied smoothly in Update()
            _targetZoom = Mathf.Clamp(_targetZoom + delta, minZoom, maxZoom);
            _lastZoomCenter = screenCenter;
        }

        private void ApplyCurrentZoom(Vector2 screenCenter, float previousZoom) {
            // Get the cursor position relative to viewport (0,0 = bottom-left, 1,1 = top-right)
            // Use canvas camera for ScreenSpaceCamera mode, null for ScreenSpaceOverlay
            Camera cam = _canvas != null ? _canvas.worldCamera : null;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                    _viewport,
                    screenCenter,
                    cam,
                    out Vector2 viewportLocalPoint);

            Vector2 viewportSize = _viewport.rect.size;
            Vector2 cursorViewportNorm = new Vector2(
                    (viewportLocalPoint.x + viewportSize.x * _viewport.pivot.x) / viewportSize.x,
                    (viewportLocalPoint.y + viewportSize.y * _viewport.pivot.y) / viewportSize.y);

            // Calculate content size before and after zoom
            Vector2 contentSizeBefore = _content.rect.size * previousZoom;
            Vector2 contentSizeAfter = _content.rect.size * _currentZoom;

            // Calculate the scrollable range (content size - viewport size)
            Vector2 scrollRangeBefore = contentSizeBefore - viewportSize;
            Vector2 scrollRangeAfter = contentSizeAfter - viewportSize;

            // Get current scroll offset in pixels
            Vector2 scrollOffsetBefore = new Vector2(
                    scrollRangeBefore.x > 0 ? _scrollRect.normalizedPosition.x * scrollRangeBefore.x
                                            : 0,
                    scrollRangeBefore.y > 0 ? _scrollRect.normalizedPosition.y * scrollRangeBefore.y
                                            : 0);

            // The point under cursor in content space (from bottom-left of content)
            Vector2 cursorContentPoint = scrollOffsetBefore + cursorViewportNorm * viewportSize;

            // After zoom, where should scroll offset be to keep same content point under cursor?
            float zoomRatio = _currentZoom / previousZoom;
            Vector2 cursorContentPointAfter = cursorContentPoint * zoomRatio;
            Vector2 scrollOffsetAfter = cursorContentPointAfter - cursorViewportNorm * viewportSize;

            // Apply the zoom scale
            _content.localScale = Vector3.one * _currentZoom;

            // Update scrollability based on new zoom level
            UpdateScrollability();

            // Convert back to normalized position
            if (_currentZoom > 1.0f && scrollRangeAfter.x > 0 && scrollRangeAfter.y > 0) {
                Vector2 newNormalizedPos = new Vector2(
                        Mathf.Clamp01(scrollOffsetAfter.x / scrollRangeAfter.x),
                        Mathf.Clamp01(scrollOffsetAfter.y / scrollRangeAfter.y));
                _scrollRect.normalizedPosition = newNormalizedPos;
            } else if (_currentZoom <= 1.0f) {
                // At zoom 1, center the content
                _scrollRect.normalizedPosition = new Vector2(0.5f, 0.5f);
            }
        }

        private void UpdateScrollability() {
            // Only allow scrolling when zoomed in
            bool canScroll = _currentZoom > 1.0f;
            _scrollRect.horizontal = canScroll;
            _scrollRect.vertical = canScroll;
            _scrollRect.enabled = canScroll || !_isPinching;
        }

        public void ResetZoom() {
            _currentZoom = 1.0f;
            _targetZoom = 1.0f;
            _zoomVelocity = 0f;
            _content.localScale = Vector3.one;
            _scrollRect.normalizedPosition = new Vector2(0.5f, 0.5f);
            UpdateScrollability();
        }
    }
}
