using System;
using Services.Interfaces;
using UnityEngine;

namespace Services {
    public class InputService : MonoBehaviour, IInputService
    {
        public event Action<Vector2Int, Vector2Int> OnSwapRequested;
        public event Action<Vector2Int> OnSwitchRequested;

        private Camera inputCamera;
        private Vector2 pointerDownScreenPos;
        private bool isPointerDown;

        private readonly Vector2 boardOrigin = Vector2.zero;
        private readonly float minDrag = 0.35f;
        private readonly float cellSize = 1f;

        private void Awake() {
            this.inputCamera = Camera.main;
        }

        private void Update()
        {
            if (TryGetPrimaryPointerState(out bool isDown, out Vector2 screenPos))
            {
                if (!this.isPointerDown && isDown) {
                    this.pointerDownScreenPos = screenPos;
                }

                if (this.isPointerDown && !isDown) {
                    TryEmitSwap(this.pointerDownScreenPos, screenPos);
                }

                this.isPointerDown = isDown;
            }
            else
            {
                // No pointer available this frame (rare). Ensure we don't get stuck.
                this.isPointerDown = false;
            }
            
            #if UNITY_EDITOR
            if (TryGetSecondaryPointerClick(out screenPos)) {
                OnSwitchRequested?.Invoke(WorldToCell(this.inputCamera.ScreenToWorldPoint(screenPos)));
            }
            #endif
        }

        private void TryEmitSwap(Vector2 downScreen, Vector2 upScreen)
        {
            if (this.inputCamera is null) return;

            Vector2 downWorld = this.inputCamera.ScreenToWorldPoint(downScreen);
            Vector2 upWorld = this.inputCamera.ScreenToWorldPoint(upScreen);
            Vector2 dragWorld = upWorld - downWorld;

            if (dragWorld.magnitude < this.minDrag)
                return;

            Vector2Int fromCell = WorldToCell(downWorld);
            Vector2Int dir = DragToCardinalDirection(dragWorld);
            Vector2Int toCell = fromCell + dir;

            // Adjacency is guaranteed by construction (to = from + dir).
            // Debug.Log($"Swap {fromCell} -> {toCell}");
            OnSwapRequested?.Invoke(fromCell, toCell);
        }

        private Vector2Int WorldToCell(Vector2 worldPos)
        {
            // Convert to board-local coords first
            Vector2 local = (worldPos - this.boardOrigin) / this.cellSize;

            // Assumes cell centers lie on integer coordinates in local space
            int x = Mathf.FloorToInt(local.x + 0.5f);
            int y = Mathf.FloorToInt(local.y + 0.5f);

            return new Vector2Int(x, y);
        }

        private static Vector2Int DragToCardinalDirection(Vector2 dragWorld)
        {
            if (Mathf.Abs(dragWorld.x) >= Mathf.Abs(dragWorld.y))
                return dragWorld.x >= 0 ? Vector2Int.right : Vector2Int.left;

            return dragWorld.y >= 0 ? Vector2Int.up : Vector2Int.down;
        }

        private static bool TryGetPrimaryPointerState(out bool isDown, out Vector2 screenPosition)
        {
            // Prefer touch when present (mobile)
            if (Input.touchCount > 0)
            {
                Touch touch = Input.GetTouch(0);
                screenPosition = touch.position;

                // Treat "Moved/Stationary" as still down, and "Began" as down.
                isDown = touch.phase == TouchPhase.Began
                         || touch.phase == TouchPhase.Moved
                         || touch.phase == TouchPhase.Stationary;

                // Ended/Canceled => not down (we still report the position)
                if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
                    isDown = false;

                return true;
            }

            // Fallback to mouse (editor/desktop)
            screenPosition = Input.mousePosition;
            isDown = Input.GetMouseButton(0);
            return true;
        }

        private static bool TryGetSecondaryPointerClick(out Vector2 screenPosition) {
            if (Input.GetMouseButtonDown(1)) {
                screenPosition = Input.mousePosition;
                return true;
            }

            screenPosition = Input.mousePosition;
            return false;
        }
    }
}