using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using Unheimlich.Gameplay.DetectiveBoard;
using System.Collections.Generic;
using UnityEngine.EventSystems;
namespace Unheimlich.UI.DetectiveBoard
{
///
/// Handles mouse interaction for the detective board World Space canvas.
/// Manages hover, selection, dragging, and click forwarding for Post-It nodes.
/// Interactions are only active when the board is in inspection mode.
///
public class DetectiveBoardInteraction : MonoBehaviour
{
[Header("References")]
[SerializeField] private Camera _camera;
[SerializeField] private Canvas _boardCanvas;
[SerializeField] private RectTransform _boardArea;
[SerializeField] private LayerMask _boardLayer;
[SerializeField] private DetectiveBoardController _boardController;
[Header("Settings")]
[SerializeField] private float _minInteractionDistance = 0.1f;
[SerializeField] private float _maxInteractionDistance = 10f;
private DetectiveBoardCanvas _canvasManager;
private PostItNode _hoveredNode;
private PostItNode _selectedNode;
private bool _isDragging;
private Plane _canvasPlane;
private Vector3 _dragOffset;
private void Start()
{
if (_camera == null)
_camera = Camera.main;
if (_boardController == null)
_boardController = FindFirstObjectByType();
if (_canvasManager == null)
_canvasManager = GetComponent();
UpdateCanvasPlane();
}
private void Update()
{
if (!IsInBoardInteractionMode())
{
ClearInteractionState();
return;
}
UpdateCanvasPlane();
if (_isDragging)
{
HandleDrag();
}
else
{
HandleHover();
}
HandleClick();
}
private bool IsInBoardInteractionMode()
{
return _boardController != null && _boardController.IsBoardActive;
}
private void ClearInteractionState()
{
if (_hoveredNode != null)
{
_hoveredNode.SetHovered(false);
_hoveredNode = null;
}
if (_selectedNode != null && _isDragging)
{
_selectedNode.SetSelected(false);
_selectedNode = null;
_isDragging = false;
}
}
private void UpdateCanvasPlane()
{
if (_boardCanvas != null)
{
Vector3 planeNormal = -_boardCanvas.transform.forward;
Vector3 planePosition = _boardCanvas.transform.position;
_canvasPlane = new Plane(planeNormal, planePosition);
}
}
private void HandleHover()
{
PostItNode hitNode = RaycastForNode();
if (hitNode != _hoveredNode)
{
if (_hoveredNode != null)
{
_hoveredNode.SetHovered(false);
}
_hoveredNode = hitNode;
if (_hoveredNode != null)
{
_hoveredNode.SetHovered(true);
}
}
}
private void HandleClick()
{
if (Mouse.current == null)
return;
PostItNode hitNode = RaycastForNode();
if (Mouse.current.leftButton.wasPressedThisFrame)
{
HandleLeftClick(hitNode);
}
else if (Mouse.current.rightButton.wasPressedThisFrame && hitNode != null && _canvasManager != null)
{
_canvasManager.OnNodeRightClicked(hitNode);
}
}
private void HandleLeftClick(PostItNode hitNode)
{
if (_canvasManager != null && _canvasManager.IsConnectMode)
{
if (hitNode != null)
{
_canvasManager.OnNodeClicked(hitNode);
}
return;
}
if (_isDragging)
{
_isDragging = false;
if (_selectedNode != null && hitNode == _selectedNode)
{
return;
}
if (_selectedNode != null)
{
_selectedNode.SetSelected(false);
}
if (hitNode != null)
{
_selectedNode = hitNode;
_selectedNode.SetSelected(true);
_isDragging = true;
CalculateDragOffset();
}
else
{
_selectedNode = null;
}
}
else
{
if (hitNode != null)
{
if (_selectedNode != null && _selectedNode != hitNode)
{
_selectedNode.SetSelected(false);
}
_selectedNode = hitNode;
_selectedNode.SetSelected(true);
_isDragging = true;
CalculateDragOffset();
}
else
{
if (_selectedNode != null)
{
_selectedNode.SetSelected(false);
_selectedNode = null;
}
}
}
}
private void HandleDrag()
{
if (_selectedNode == null || _camera == null)
return;
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0));
if (_canvasPlane.Raycast(ray, out float enter))
{
Vector3 worldHitPoint = ray.GetPoint(enter);
Vector3 newWorldPos = worldHitPoint + _dragOffset;
Vector3 localPos = _boardCanvas.transform.InverseTransformPoint(newWorldPos);
Vector2 clampedPos = ClampPositionToBoard(new Vector2(localPos.x, localPos.y));
_selectedNode.SetPosition(clampedPos);
if (_canvasManager != null)
{
_canvasManager.UpdateConnectionsForNode(_selectedNode);
}
}
}
private Vector2 ClampPositionToBoard(Vector2 position)
{
if (_boardArea == null)
return position;
Rect boardRect = _boardArea.rect;
float halfWidth = boardRect.width * 0.5f;
float halfHeight = boardRect.height * 0.5f;
float clampedX = Mathf.Clamp(position.x, -halfWidth, halfWidth);
float clampedY = Mathf.Clamp(position.y, -halfHeight, halfHeight);
return new Vector2(clampedX, clampedY);
}
private void CalculateDragOffset()
{
if (_selectedNode == null || _camera == null)
return;
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0));
if (_canvasPlane.Raycast(ray, out float enter))
{
Vector3 worldHitPoint = ray.GetPoint(enter);
Vector3 currentWorldPos = _selectedNode.transform.position;
_dragOffset = currentWorldPos - worldHitPoint;
}
}
private PostItNode RaycastForNode()
{
if (_camera == null || Mouse.current == null)
return null;
Vector2 mousePos = Mouse.current.position.ReadValue();
GraphicRaycaster raycaster = _boardCanvas?.GetComponent();
if (raycaster != null && EventSystem.current != null)
{
var pointerEventData = new PointerEventData(EventSystem.current)
{
position = mousePos
};
var results = new List();
raycaster.Raycast(pointerEventData, results);
foreach (var result in results)
{
PostItNode node = result.gameObject.GetComponentInParent();
if (node != null)
return node;
}
}
Ray ray = _camera.ScreenPointToRay(new Vector3(mousePos.x, mousePos.y, 0));
if (Physics.Raycast(ray, out RaycastHit hit, _maxInteractionDistance, _boardLayer))
{
float distance = hit.distance;
if (distance >= _minInteractionDistance)
{
return hit.collider.GetComponentInParent();
}
}
return null;
}
}
}