using UnityEngine;

namespace UnityFlow.Scripts
{
    public class CameraDragAndZoomCtl : MonoBehaviour
    {
        [Header("Main Objects")] public Camera targetCamera;

        [Tooltip("Overlay Canvas UI Image Object")]
        public RectTransform observeWindow;

        [Tooltip("Target Reference Map Object With World Space Canvas")]
        public RectTransform referenceObject;

        [Header("Zoom Boundary Settings")] public bool enableZoom = true;
        public bool autoZoom = false;
        public float zoomSpeed = 0.5f;
        public float zoomUpperBound = 10f;
        public float zoomLowerBound = 2f;

        [Header("Drag Settings")] public bool enableDrag = true;
        public bool snapBoundary = true;
        public bool isDragging = false;
        public KeyCode DragKey = KeyCode.Mouse2;
        private Vector3 dragStartMousePosition;
        private Vector3 dragStartCameraPosition;

        // Variable Parameters
        private Rect rfRect;
        private Rect obRect;

        private void Start()
        {
            MappingUIParameter();
        }

        void Update()
        {
            Zoom();
            Drag();
            if (snapBoundary)
            {
                SnapBoundary();
            }
        }

        private void MappingUIParameter()
        {
            rfRect = GetScreenRectOfWorldUI(referenceObject, targetCamera);
            obRect = GetScreenRectOfScreenUI(observeWindow);

            if (!autoZoom) return;
            zoomUpperBound = targetCamera.orthographicSize *
                             Mathf.Min(rfRect.width / obRect.width, rfRect.height / obRect.height) * 0.9f;
            zoomLowerBound = zoomUpperBound / 2;

            zoomSpeed = zoomUpperBound * 0.1f;
        }

        private void Drag()
        {
            if (Input.GetKeyDown(DragKey))
            {
                isDragging = true;
                dragStartMousePosition = GetMouseScreenPosition();
                dragStartCameraPosition = transform.position;
            }

            if (Input.GetKeyUp(DragKey))
            {
                isDragging = false;
            }

            if (isDragging)
            {
                // Calculate the distance between the start and current mouse position in world space
                Vector2 dragCurrentMousePosition = GetMouseScreenPosition();
                Vector3 distanceInWorldSpace = targetCamera.ScreenToWorldPoint(dragStartMousePosition) -
                                               targetCamera.ScreenToWorldPoint(dragCurrentMousePosition);

                // Update Camera Position
                Vector3 newPos = dragStartCameraPosition + distanceInWorldSpace;
                targetCamera.transform.position = new Vector3(newPos.x, newPos.y, transform.position.z);
            }
        }

        private void Zoom()
        {
            float size = targetCamera.orthographicSize;
            Vector2 screenMP = GetMouseScreenPosition();
            Vector2 worldMP = targetCamera.ScreenToWorldPoint(screenMP);

            // Zoom out
            if (Input.mouseScrollDelta.y < 0 && size < zoomUpperBound)
            {
                // Adjust Orthographic Size
                targetCamera.orthographicSize += zoomSpeed;
                targetCamera.orthographicSize = targetCamera.orthographicSize > zoomUpperBound
                    ? zoomUpperBound
                    : targetCamera.orthographicSize;
            }

            // Zoom in
            if (Input.mouseScrollDelta.y > 0 && size > zoomLowerBound)
            {
                // Adjust Orthographic Size
                targetCamera.orthographicSize -= zoomSpeed;
                targetCamera.orthographicSize = targetCamera.orthographicSize < zoomLowerBound
                    ? zoomLowerBound
                    : targetCamera.orthographicSize;
            }

            // Update Camera Position
            transform.Translate(worldMP - (Vector2)targetCamera.ScreenToWorldPoint(screenMP));
        }


        private void SnapBoundary()
        {
            Rect rfRect2 = GetScreenRectOfWorldUI(referenceObject, targetCamera);

            float dx = Mathf.Max(0, rfRect2.xMin - obRect.xMin) - Mathf.Max(0, obRect.xMax - rfRect2.xMax);
            float dy = Mathf.Max(0, rfRect2.yMin - obRect.yMin) - Mathf.Max(0, obRect.yMax - rfRect2.yMax);

            Vector2 d = new Vector2(dx, dy);
            d = targetCamera.ScreenToWorldPoint(d) - targetCamera.ScreenToWorldPoint(new Vector2(0, 0));
            targetCamera.transform.Translate(d);
        }


        private static Rect GetScreenRectOfWorldUI(RectTransform rt, Camera camera)
        {
            var wCorners = new Vector3[4];
            var sCorners = new Vector3[4];
            
            rt.GetWorldCorners(wCorners);
            for (var i = 0; i < sCorners.Length; i++)
            {
                sCorners[i] = camera.WorldToScreenPoint(wCorners[i]);
            }
            
            var position = (Vector2)sCorners[0];
            var size = sCorners[2] - sCorners[0];
            return new Rect(position, size);
        }

        private static Rect GetScreenRectOfScreenUI(RectTransform rt)
        {
            var wCorners = new Vector3[4];
            rt.GetWorldCorners(wCorners);
            var position = (Vector2)wCorners[0];
            var size = wCorners[2] - wCorners[0];
            return new Rect(position, size);
        }

        private static Vector2 GetMouseScreenPosition()
        {
            Vector3 mousePos = Input.mousePosition;
            return mousePos;
        }

    }
}