using System.Collections.Generic;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using UnityEngine.UIElements;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Unit;
using VOID.UserInterface.Game.Util;
using VOID.ScriptableObjects;

namespace VOID.UserInterface.Game.Party
{
    /// <summary>
    /// Manages all visual elements for one <see cref="UnitObject"/> in <see cref="PartyUI"/>, including: portrait and statuses
    /// </summary>
    public class PartyUnitUI : MonoBehaviour
    {
        [ShowInInspector, ReadOnly] public UnitObject unit { get; private set; }

        // Visual Elements
        private VisualElement _templateElement;
        private VisualElement _statusRow;
        private UnitPortraitUI _unitPortraitUI;
        private Dictionary<BasicStatus, StatusUI> _statuses = new();

        /// <summary>
        /// Initializes whole <see cref="PartyUnitUI"/>, should be executed from <see cref="PartyUI"/>.
        /// </summary>
        /// <param name="partyUnitTemplate">visual element which already exists and will be used.</param>
        public void Init(VisualElement partyUnitTemplate)
        {
            // Find all important elements
            _templateElement = partyUnitTemplate;
            _statusRow = _templateElement.Q("status-row");
            _unitPortraitUI = new UnitPortraitUI();
            _unitPortraitUI.Init(_templateElement.Q("unit-portrait"));
            _templateElement.style.display = DisplayStyle.None;

            // Clear all placeholder statuses
            _statusRow.Clear();
        }

        /// <summary>
        /// Given <see cref="UnitObject"/> will be used in this visual element.
        /// Which means its portrait, statuses and health will be displayed.
        /// </summary>
        public void SetUnit(UnitObject unit)
        {
            // Remove everything about previous Unit
            UnsetUnit(); 

            // New Unit assigned - start listeners and wait
            this.unit = unit;
            this.unit.basicEvents.onStatusStart.AddListener(OnUnitStatusStart);
            this.unit.basicEvents.onStatusEnd.AddListener(OnUnitStatusEnd);
            
            // Make UI visible and set Unit's portrait
            _templateElement.style.display = DisplayStyle.Flex;

            // For all statuses that already exists we need to initialize them manually
            this.unit.statusManager.GetStatuses().ForEach(status => OnUnitStatusStart(new StatusEvent { obj = unit, status = status }));
            
            _unitPortraitUI.SetUnit(unit);
        }

        /// <summary>
        /// Clears and hides this element.
        /// </summary>
        public void UnsetUnit()
        {
            // There is no unit currently
            if (!unit) return;

            // Remove all listeners, then remove assigned Unit
            unit.basicEvents.onStatusStart.RemoveListener(OnUnitStatusStart);
            unit.basicEvents.onStatusEnd.RemoveListener(OnUnitStatusEnd);
            unit = null;
            
            // Make UI invisible and unset portrait
            _templateElement.style.display = DisplayStyle.None;
            
            // Remove mapping
            _statuses = null;
            
            // Clear all statuses
            _statusRow.Clear();
            
            _unitPortraitUI.UnsetUnit();
        }

        /// <summary>
        /// Adds class "active" which usually will be used, by portret of player's currently active <see cref="UnitObject"/>
        /// </summary>
        public void SetActive(bool active = true)
        {
            _templateElement.EnableInClassList("active", active);
        }

        private void OnUnitStatusStart(StatusEvent statusEvent)
        {
            if (statusEvent.status.hidden) return;
            var statusUI = new StatusUI();
            statusUI.InitNew(_statusRow, statusEvent.status);
            _statuses.Add(statusEvent.status, statusUI);
        }
        
        private void OnUnitStatusEnd(StatusEvent statusEvent)
        {
            if (statusEvent.status.hidden) return;
            _statuses.GetValueOrDefault(statusEvent.status)?.Destroy();
            _statuses.Remove(statusEvent.status);
        }
    }
}
