using System;
using System.Linq;
using Schema;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Dict.Ability;
using VOID.Generic.Util;
using VOID.ScriptableObjects.Abilities;
using Action = Schema.Action;

namespace VOID.Generic.AI.Schema.Actions
{
    [Description("Gets ability by selected filters from this unit and save it as blackboard variable")]
    [Category(SchemaCategory.ThisUnit)]
    public class GetAbility : Action
    {
        private enum SearchType
        {
            First,
            Random,
            LeastExpensive,
            MostExpensive,
            GreatestRange
        }
        
        private enum AbilityPurposeType
        {
            Any,
            All
        }

        [SerializeField] private bool includeBasicAttack;
        [SerializeField] private BlackboardEntrySelector<float> atLeastRange;
        [SerializeField] private AbilityPurposeType abilityPurposeType;
        [SerializeField] private AbilityPurposeFlag abilityPurpose;
        [SerializeField] private SearchType searchBy;
        [SerializeField, WriteOnly, Space(15)] private BlackboardEntrySelector<BasicAbility> saveTo;

        public override NodeStatus Tick(object nodeMemory, SchemaAgent agent)
        {
            var thisUnit = ((UnitObjectSchemaAgent)agent).unit;
            var allAbilities = thisUnit.unitAbilityBook.abilities.ToList();

            // Unit dont have any abilities
            if (allAbilities.IsNullOrEmpty()) return NodeStatus.Failure;

            if (includeBasicAttack) allAbilities.Add(thisUnit.unitAbilityBook.attackAbility);

            // Filter by range required
            allAbilities = allAbilities.Where(ability => ability.range >= atLeastRange.value).ToList();
            
            // Filter by wanted purpose
            allAbilities = allAbilities.Where(ability =>
            {
                switch (abilityPurposeType)
                {
                    case AbilityPurposeType.All when (ability.abilityPurpose & abilityPurpose) == abilityPurpose:
                    case AbilityPurposeType.Any when (ability.abilityPurpose & abilityPurpose) != AbilityPurposeFlag.None:
                        return true;
                    default:
                        return false;
                }
            }).ToList();

            // Getting final ability and saving it
            saveTo.value = searchBy switch
            {
                SearchType.First => allAbilities.FirstOrDefault(),
                SearchType.Random => RandomUtil.RandomElement(allAbilities),
                SearchType.LeastExpensive => allAbilities.Aggregate(allAbilities.First(),
                    (a1, a2) => a2.actionPointCost > a1.actionPointCost ? a1 : a2),
                SearchType.MostExpensive => allAbilities.Aggregate(allAbilities.First(),
                    (a1, a2) => a2.actionPointCost <= a1.actionPointCost ? a1 : a2),
                SearchType.GreatestRange => allAbilities.Aggregate(allAbilities.First(),
                    (a1, a2) => a2.range <= a1.range ? a1 : a2),
                _ => throw new ArgumentOutOfRangeException()
            };

            if (!saveTo.value) return NodeStatus.Failure;

            return NodeStatus.Success;
        }
    }
}