using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Events;
using VOID.UserInterface.Game.LevelUp;
using VOID.Generic.Util;
using VOID.ScriptableObjects;
using VOID.ScriptableObjects.Player;

namespace VOID.Generic.Player
{
    /// <summary>
    /// Manages Player's main <see cref="UnitObject"/> exp and level. Also manages sources which increases experience. 
    /// </summary>
    [DefaultExecutionOrder(-1)]
    public class PlayerLevelManager : MonoBehaviour
    {
        public static PlayerLevelManager current { get; private set; }
        
        // Cache
        private UnitObject _mainUnit;
        private Dictionary<LevelUpOption, int> _selectedOptionsCounter = new();
        private List<LevelUpOption> _optionsAvailable = new();
        private List<LevelUpOption> _optionsForSelection = new();
        private int _remainingLevelUps;

        // Config
        [SerializeField] private List<LevelUpOption> _options = new();
        [SerializeField, MinValue(1f)] private int _optionsPerLevel = 3;
        
        private void Awake()
        {
            current = this;
            _optionsAvailable = _options.ToList();
        }

        private void Start()
        {
            PlayerController.current.OnMainUnitChange += OnMainUnitChange;
        }

        private void OnMainUnitChange(UnitObject unitObject)
        {
            if (_mainUnit)
            {
                _mainUnit.unitEvents.onLevelGain.RemoveListener(OnLevelUp);
            }
            
            _mainUnit = unitObject;
            _mainUnit.unitEvents.onLevelGain.AddListener(OnLevelUp);
        }

        private void OnLevelUp(LevelGainEvent levelGainEvent)
        {
            _remainingLevelUps += levelGainEvent.amount;
            
            OpenLevelUpChoice();
        }

        public void OpenLevelUpChoice()
        {
            // No level ups to use - do nothing
            if (_remainingLevelUps <= 0) return;
            
            if (_optionsAvailable.Count <= 0)
            {
                Debug.LogWarning($"No more {nameof(LevelUpOption)} available");
                return;
            }
            
            LevelUpUI.current.Open(GetOptionsForSelection(), OnLevelUpOptionSelect);            
        }

        private List<LevelUpOption> GetOptionsForSelection()
        {
            // Get cached if available (when player closed previous choice without selecting)
            if (_optionsForSelection.Count > 0) return _optionsForSelection;
            
            var optionsAvailable = _optionsAvailable.ToList();
            _optionsForSelection.Clear();

            // Rebuild options for selection without repeatations
            while (true)
            {
                // No more available options
                if (optionsAvailable.Count <= 0) break;
                
                // Amount of wanted options achieved 
                if (_optionsForSelection.Count >= _optionsPerLevel) break;

                // Builds options to select by player
                var randomOption = RandomUtil.RandomElementByWeight(optionsAvailable, option => option.weight);
                _optionsForSelection.Add(randomOption);
                optionsAvailable.Remove(randomOption);
            }

            return _optionsForSelection;
        }

        private void OnLevelUpOptionSelect(LevelUpOption option)
        {
            var status = ScriptableObject.CreateInstance<BasicStatus>();
            status.displayName = option.displayName;
            status.displayIcon = option.displayIcon;
            status.description = option.displayDescription;
            status.effects = option.effects.ToList();
            status.hidden = true;
            status.permanent = true;
            
            _mainUnit.statusManager.AddStatus(status);
            
            _selectedOptionsCounter.TryAdd(option, 0);
            _selectedOptionsCounter[option]++;

            // Remove options from pool when it reached its availability limit
            if (_selectedOptionsCounter[option] >= option.limit) _optionsAvailable.Remove(option);
            
            // Clear cache so it can be rebuild again
            _optionsForSelection.Clear();

            // Checking if we got more level ups to distribute
            _remainingLevelUps--;
            if (_remainingLevelUps > 0) OpenLevelUpChoice(); 
        }
    }
}