using System;
using System.Collections.Generic;
using QuickOutline;
using Sirenix.OdinInspector;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract;
using VOID.Generic.Objects.Item;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Player.Input;
using VOID.Generic.Player.Util;
using VOID.Generic.Triggers;
using VOID.UserInterface.Game.Party;
using VOID.Generic.Util;
using VOID.Generic.World;

namespace VOID.Generic.Player
{
    /// <summary>
    /// Manages Highlights and Outlines of <see cref="UnitObject"/>s and <see cref="ItemObject"/>s in 2 modes:<br/>
    /// - When cursor is over specific object - highlight that object<br/>
    /// - When button is pressed - highlight all objects in range<br/>
    /// <br/>
    /// <see cref="UnitObject"/> can be selected from SCENE or <see cref="UnitPortraitUI"/>
    /// </summary>
    [DefaultExecutionOrder(-1)]
    public class HighlightManager : MonoBehaviour, IDynamicTrigger
    {
        public static HighlightManager current { get; private set; }
        
        [Title("Shaders used")]
        [SerializeField] public Shader[] shaders;
        
        [Title("Colors")]
        [SerializeField] private Color _enemyColor;
        [SerializeField] private Color _cautiousColor;
        [SerializeField] private Color _neutralColor;
        [SerializeField] private Color _friendlyColor;
        [SerializeField] private Color _companionColor;
        [SerializeField] private Color _selfColor;
        [SerializeField] private Color _itemColor;
        
        [Title("Outline")]
        [SerializeField] private float _outlineWidth;
        [SerializeField] private Outline.Mode _outlineMode;
        
        [Title("Group Outline")]
        [SerializeField] private float _range = Area.Size;
        
        // Runtime
        private GameObject _highlightGameObject;
        private SphereCollider _collider;
        private Transform _stickTo;
        private readonly List<UnitObject> _unitsInRange = new();
        // private readonly List<AbstractObject> _objectsInRange = new();
        private AbstractObject _objectUnderCursor;
        
        private void Awake()
        {
            current = this;
            
            _highlightGameObject = new GameObject("Highlight Range");
            _highlightGameObject.transform.parent = gameObject.transform;
            _highlightGameObject.layer = LayerManager.TriggerIndex;
            _collider = _highlightGameObject.AddComponent<SphereCollider>();
            _collider.radius = _range;
            _collider.isTrigger = true;
        }

        private void Start()
        {
            PlayerController.current.OnActiveUnitChange += OnActiveUnitChange;
            PlayerInputManager.current.OnHighlightStart += ShowHighlightForAllInRange;
            PlayerInputManager.current.OnHighlightEnd += HideHighlightForAllInRange;
        }

        private void FixedUpdate()
        {
            StickTo();
            HighlightUnderCursor();
        }

        private void StickTo()
        {
            if (_stickTo) _highlightGameObject.transform.position = _stickTo.position;
        }

        private void HighlightUnderCursor()
        {
            // Find whats under cursor, we allow only ItemObject (from scene) and UnitObject (from scene and portraits)
            var select = SelectUtil.Select();
            
            // There is nothing under cursor, but something was previous frame!
            if (!select.selectObject && _objectUnderCursor)
            {
                HideHighlight(_objectUnderCursor);
                _objectUnderCursor = null;
            }

            // Nothing to highlight
            if (!select.selectObject) return;
            
            // This one was already under cursor and is highlighted previously
            if (select.selectObject == _objectUnderCursor) return;
            
            // There is something new under cursor - hide previous object highlight and continue
            if (_objectUnderCursor) HideHighlight(_objectUnderCursor);

            _objectUnderCursor = select.selectObject;
            ShowHighlight(_objectUnderCursor);
        }

        private void OnActiveUnitChange(UnitObject unitObject)
        {
            _stickTo = unitObject.transform;
        }

        public void OnObjectEnter(AbstractObject obj)
        {
            if (obj is not UnitObject unit) return;
            _unitsInRange.Add(unit);
            if (PlayerInputManager.current.isHighlight) ShowHighlight(unit);
        }

        public void OnObjectExit(AbstractObject obj)
        {
            if (obj is not UnitObject unit) return;
            _unitsInRange.Remove(unit);
            if (PlayerInputManager.current.isHighlight) HideHighlight(unit);
        }

        private void ShowHighlight(AbstractObject obj)
        {
            // Highlighting only when object allow that on itself 
            if (!obj.basicData.canBeSelected) return;
            
            var outline = obj.gameObject.GetComponent<Outline>();
            if (!outline) outline = obj.gameObject.AddComponent<Outline>();

            if (obj is UnitObject unit) outline.OutlineColor = GetColorForUnit(unit);
            else outline.OutlineColor = _itemColor;
            outline.OutlineMode = _outlineMode;
            outline.OutlineWidth = _outlineWidth;
            outline.enabled = true;
        }

        private void HideHighlight(AbstractObject obj)
        {
            if (PlayerInputManager.current.isHighlight && obj is UnitObject unit && _unitsInRange.Contains(unit)) return;
            if (obj.TryGetComponent<Outline>(out var outline)) outline.enabled = false;
        }

        private void ShowHighlightForAllInRange()
        {
            _unitsInRange.ForEach(ShowHighlight);
        }

        private void HideHighlightForAllInRange()
        {
            _unitsInRange.ForEach(HideHighlight);
        }

        private Color GetColorForUnit(UnitObject unit)
        {
            if (!PlayerController.current.activeUnit) return _neutralColor;
            if (unit == PlayerController.current.activeUnit) return _selfColor;
            var attitudeType = PlayerController.current.activeUnit.unitAttitude.GetAttitudeWith(unit);
            return attitudeType switch
            {
                AttitudeType.Hostile => _enemyColor,
                AttitudeType.Cautious => _cautiousColor,
                AttitudeType.Neutral => _neutralColor,
                AttitudeType.Friendly => _friendlyColor,
                AttitudeType.Companion => _companionColor,
                _ => throw new ArgumentOutOfRangeException(nameof(attitudeType), attitudeType, null)
            };
        }
    }
}