using UnityEngine;
using UnityEngine.Events;

namespace Genesis.POISystem
{
    public class POIManager : MonoBehaviour
    {
        public static POIManager Instance { get; private set; }

        [SerializeField] UnityEvent<IInteractablePOI> onStartPointingPOIEvent;

        public UnityEvent<IInteractablePOI> OnStartPointingPOIEvent => onStartPointingPOIEvent;

        [SerializeField] UnityEvent<IInteractablePOI> onStopPointingPOIEvent;

        public UnityEvent<IInteractablePOI> OnStopPointingPOIEvent => onStopPointingPOIEvent;

        [SerializeField] UnityEvent<IInteractablePOI> onSelectPOIEvent;

        public UnityEvent<IInteractablePOI> OnSelectPOIEvent => onSelectPOIEvent;

        [SerializeField] UnityEvent<IInteractablePOI> onDeselectPOIEvent;

        public UnityEvent<IInteractablePOI> OnDeselectPOIEvent => onDeselectPOIEvent;

        public IInteractablePOI SelectedPOI { get; private set; }
        public IInteractablePOI PointedPOI { get; private set; }

        [SerializeField] LayerMask interactablePOILayerMask;

        void Awake()
        {
            if (Instance != null && Instance != this) Destroy(gameObject);
            else Instance = this;
        }

        void Update()
        {
            HandlePOIPointing();
            HandlePOISelection();
        }

        void HandlePOIPointing()
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, interactablePOILayerMask))
            {
                if (hit.collider.TryGetComponent(out IInteractablePOI poi))
                {
                    if (PointedPOI != poi)
                        HandlePointingToPOI(poi);
                }

                return;
            }
            HandleStopPointingToCurrentPOI();
        }

        void HandlePOISelection()
        {
            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, interactablePOILayerMask))
                {
                    if (hit.collider.TryGetComponent(out IInteractablePOI poi))
                    {
                        if (SelectedPOI != poi)
                            HandleSelectPOI(poi);
                    }

                    return;
                }
                else
                    HandleDeselectCurrentPOI();
            }
        }

        void HandlePointingToPOI(IInteractablePOI poi)
        {
            if (PointedPOI != null)
            {
                PointedPOI.StopPointing();
                onStopPointingPOIEvent?.Invoke(PointedPOI);
            }

            PointedPOI = poi;
            PointedPOI.StartPointing();
            onStartPointingPOIEvent?.Invoke(PointedPOI);
        }

        void HandleStopPointingToCurrentPOI()
        {
            if (PointedPOI != null)
            {
                PointedPOI.StopPointing();
                onStopPointingPOIEvent?.Invoke(PointedPOI);

                PointedPOI = null;
            }
        }

        void HandleSelectPOI(IInteractablePOI poi)
        {
            if (SelectedPOI != null)
            {
                SelectedPOI.Deselect();
                onDeselectPOIEvent?.Invoke(SelectedPOI);
            }

            SelectedPOI = poi;
            SelectedPOI.Select();
            onSelectPOIEvent?.Invoke(SelectedPOI);
        }

        void HandleDeselectCurrentPOI()
        {
            if (SelectedPOI != null)
            {
                SelectedPOI.Deselect();
                onDeselectPOIEvent?.Invoke(SelectedPOI);

                SelectedPOI = null;
            }
        }
    }
}