using System;
using System.Collections.Generic;
using ZLinq;

namespace Nox.Game {

    public interface IModifierResolver {
        int Resolve(int baseValue, IEnumerable<IModifier> modifiers, IEntityDefinition entity = null);
        IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType);
        IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType);
        IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType);
    }

    /// <summary>
    /// Collects modifiers from an entity's direct modifiers and perk-granted modifiers,
    /// then resolves them against a base value.
    ///
    /// Resolution order:
    ///   1. Flat     — sum of all flat values replaces the base
    ///   2. Addition — summed and added to the running total
    ///   3. Percentage — summed into a single multiplier applied to the post-addition total
    ///   4. Multiplication — each factor applied sequentially to the running total
    ///
    /// If a modifier has a ScalingSource set, its Value is multiplied by the entity's
    /// current value for that source before being applied. For example, a modifier with
    /// Target=Health, ScalingSource=Might, Operation=Addition, Value=2 means "+2 Health
    /// per point of Might".
    /// </summary>
    public sealed class ModifierResolver : IModifierResolver {
        public int Resolve(int baseValue, IEnumerable<IModifier> modifiers, IEntityDefinition entity = null) {
            if(modifiers == null) {
                return baseValue;
            }

            float flatSum = 0f;
            float addSum = 0f;
            float pctSum = 0f;
            var mulValues = new List<float>();
            var hasFlat = false;

            foreach(var m in modifiers) {
                if(m == null || m.Operation == ModifierOperation.None) {
                    continue;
                }

                var effectiveValue = ResolveScaling(m, entity);

                switch(m.Operation) {
                    case ModifierOperation.Flat:
                        flatSum += effectiveValue;
                        hasFlat = true;
                        break;
                    case ModifierOperation.Addition:
                        addSum += effectiveValue;
                        break;
                    case ModifierOperation.Percentage:
                        pctSum += effectiveValue;
                        break;
                    case ModifierOperation.Multiplication:
                        mulValues.Add(effectiveValue);
                        break;
                }
            }

            float result = hasFlat ? flatSum : baseValue;
            result += addSum;
            result *= 1f + (pctSum / 100f);

            foreach(var mul in mulValues) {
                result *= mul;
            }

            return (int)Math.Round(result);
        }

        private static float ResolveScaling(IModifier modifier, IEntityDefinition entity) {
            var value = modifier.Value;
            if(entity == null || modifier.ScalingSource == null || !modifier.ScalingSource.IsSet) {
                return value;
            }

            var source = modifier.ScalingSource;
            var sourceValue = source.Type switch {
                ModifierTargetType.Attribute when entity.Attributes?.attributes != null =>
                    entity.Attributes.GetValue(source.AttributeType),
                ModifierTargetType.Stat when entity.Stats?.stats != null =>
                    entity.Stats.GetValue(source.StatType),
                _ => 0
            };

            return value * sourceValue;
        }

        public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, StatType statType) {
            if(entity == null) {
                return Array.Empty<IModifier>();
            }

            var result = new List<IModifier>();
            CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(statType));
            return result;
        }

        public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, AttributeType attributeType) {
            if(entity == null) {
                return Array.Empty<IModifier>();
            }

            var result = new List<IModifier>();
            CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(attributeType));
            return result;
        }

        public IEnumerable<IModifier> CollectModifiers(IEntityDefinition entity, CombatScoreType combatScoreType) {
            if(entity == null) {
                return Array.Empty<IModifier>();
            }

            var result = new List<IModifier>();
            CollectFromEntity(entity, result, m => m.Target != null && m.Target.Matches(combatScoreType));
            return result;
        }

        private static bool MeetsRequirements(IModifier modifier, IEntityDefinition entity) {
            var requirements = modifier.Requirements;
            if(requirements == null || requirements.Count == 0) {
                return true;
            }
            for(int i = 0; i < requirements.Count; i++) {
                if(!requirements[i].IsMet(entity.Attributes)) {
                    return false;
                }
            }
            return true;
        }

        private static void CollectFromEntity(IEntityDefinition entity, List<IModifier> result, Func<IModifier, bool> predicate) {
            if(entity.Modifiers?.modifiers != null) {
                foreach(var m in entity.Modifiers.modifiers) {
                    if(m != null && predicate(m) && MeetsRequirements(m, entity)) {
                        result.Add(m);
                    }
                }
            }

            if(entity.Perks?.perks != null) {
                foreach(var p in entity.Perks.perks) {
                    if(p?.Modifiers?.modifiers == null) {
                        continue;
                    }
                    foreach(var m in p.Modifiers.modifiers) {
                        if(m != null && predicate(m)) {
                            result.Add(m);
                        }
                    }
                }
            }
        }
    }
}
