using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using BITKit;
using BITKit.Entities;
using BITKit.WorldNode;
using Cysharp.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;


namespace Net.Project.B.Health
{
    public interface IHealthComponent
    {
        public int MaxHealthPoint { get; }
    }

    [Serializable]
    public class HealthComponent:IHealthComponent,IWorldNode
    {
        public int maxHealthPoint = 100;
        public int MaxHealthPoint => maxHealthPoint;
    }
    /// <summary>
    /// 生命值服务
    /// </summary>
    public interface IHealthService
    {
        /// <summary>
        /// 已缓存的生命值
        /// </summary>
        public IReadOnlyDictionary<int,int> Healths { get; }
        /// <summary>
        /// 在改变生命值前,ID,当前生命值,新的生命值,来源
        /// <returns>增加的数值</returns>
        /// </summary>
        public event Func<int,int,int,object,int> OnHealthPlus;
        /// <summary>
        /// 在改变生命值后
        /// </summary>
        /// /// <summary>
        /// 当健康值发生变化时触发的事件。
        /// </summary>
        public event Action<int,int,int,object> OnHealthChanged;
        /// <summary>
        /// 调整生命值
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value">+- not set</param>
        /// <returns></returns>
        public UniTask<int> AddHealth(int id,int value,object arg);
    }
    public class HealthService:IHealthService,IDisposable
    {    private static HealthService _singleton;
        private readonly IEntitiesService _entitiesService;

        private static readonly ConcurrentDictionary<int, IHealthComponent> HealthComponents = new();
    
        public HealthService(IEntitiesService entitiesService)
        {
            _entitiesService = entitiesService;
            _singleton = this;

            _entitiesService.OnAdd += OnAdd;
        }

        private void OnAdd(IEntity obj)
        {
            if (obj.ServiceProvider.GetService<IHealthComponent>() is {} healthComponent)
            {
                if (HealthComponents.TryAdd(obj.Id, healthComponent))
                {
                    Healths.TryAdd(obj.Id, healthComponent.MaxHealthPoint <=0 ? 100 : healthComponent.MaxHealthPoint);
                }
            }
        }

        [BITCommand]
        public static void SetHealth(int id, int newHealth)
        {
            Healths.GetOrAdd(id,100);
            Healths.Set(id,newHealth);
            OnHealthChanged?.Invoke(id,newHealth,newHealth,null);
        }
        [BITCommand]
        public static void AddHealth(int id, int newHealth)
        {
            Healths.GetOrAdd(id,100);
            _singleton.AddHealth(id, newHealth, null).Forget();
            
        }
        
  
        IReadOnlyDictionary<int, int> IHealthService.Healths => Healths;
        private static event Func<int,int,int,object,int> OnHealthChange;
        private static event Action<int,int,int,object> OnHealthChanged;
        private static readonly ConcurrentDictionary<int, int> Healths = new();
        
        event Func<int,int,int,object,int> IHealthService.OnHealthPlus
        {
            add => OnHealthChange += value;
            remove => OnHealthChange -= value;
        }
        event Action<int,int,int,object> IHealthService.OnHealthChanged
        {
            add => OnHealthChanged += value;
            remove => OnHealthChanged -= value;
        }
        public UniTask<int> AddHealth(int id, int value,object arg)
        {
            if (Healths.TryGetValue(id,out var current) is false) return UniTask.FromResult(-1);
            
            foreach (var func in OnHealthChange.CastAsFunc())
            {
                value =func.Invoke(id,current, value, arg);
            }
            var newHp = Math.Clamp(current + value, -1, HealthComponents[id].MaxHealthPoint);
            Healths.Set(id,newHp);
            OnHealthChanged?.Invoke(id,current,newHp,arg);
            return UniTask.FromResult(newHp);
        }

        public void Dispose()
        {
            _entitiesService.OnAdd -= OnAdd;
        }
    }
}
