using SpineViewer.Spine;
using SpineViewer.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SpineViewer.Spine.SpineView
{
    /// <summary>
    /// 用于在 PropertyGrid 上显示插槽附件加载情况包装类
    /// </summary>
    public class SpineSlotProperty(SpineObject spine) : ICustomTypeDescriptor
    {
        [Browsable(false)]
        public SpineObject Spine { get; } = spine;

        /// <summary>
        /// 显示所有插槽集合
        /// </summary>
        public override string ToString() => $"[{string.Join(", ", Spine.SlotAttachmentNames.Keys)}]";

        public override bool Equals(object? obj)
        {
            if (obj is SpineAnimationProperty prop) return ToString() == prop.ToString();
            return base.Equals(obj);
        }

        public override int GetHashCode() => HashCode.Combine(typeof(SpineAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode());

        #region ICustomTypeDescriptor 接口实现

        // XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确

        /// <summary>
        /// 属性描述符缓存
        /// </summary>
        private static readonly Dictionary<string, SlotPropertyDescriptor> pdCache = [];

        public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
        public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
        public string? GetComponentName() => TypeDescriptor.GetComponentName(this, true);
        public TypeConverter? GetConverter() => TypeDescriptor.GetConverter(this, true);
        public EventDescriptor? GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
        public PropertyDescriptor? GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
        public object? GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
        public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
        public EventDescriptorCollection GetEvents(Attribute[]? attributes) => TypeDescriptor.GetEvents(this, attributes, true);
        public object? GetPropertyOwner(PropertyDescriptor? pd) => this;
        public PropertyDescriptorCollection GetProperties() => GetProperties(null);
        public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
        {
            var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
            foreach (var slotName in Spine.SlotAttachmentNames.Keys)
            {
                if (!pdCache.TryGetValue(slotName, out var pd))
                    pdCache[slotName] = pd = new SlotPropertyDescriptor(slotName, [new DisplayNameAttribute($"{slotName}")]);
                props.Add(pd);
            }
            return props;
        }

        /// <summary>
        /// 插槽属性描述符, 实现对属性的读取和赋值
        /// </summary>
        internal class SlotPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes)
        {
            public override Type ComponentType => typeof(SpineSlotProperty);
            public override bool IsReadOnly => false;
            public override Type PropertyType => typeof(SlotProperty);
            public override bool CanResetValue(object component) => false;
            public override void ResetValue(object component) { }
            public override bool ShouldSerializeValue(object component) => false;

            /// <summary>
            /// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
            /// </summary>
            public override object? GetValue(object? component)
            {
                if (component is SpineSlotProperty slots)
                    return slots.Spine.GetSlotAttachment(Name);
                return null;
            }

            /// <summary>
            /// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
            /// </summary>
            public override void SetValue(object? component, object? value)
            {
                if (component is SpineSlotProperty slots)
                {
                    if (value is string s)
                        slots.Spine.SetSlotAttachment(Name, s);
                }
            }
        }

        #endregion
    }

    /// <summary>
    /// 对 <c><see cref="SpineSlotProperty"/>.{name}</c> 属性的包装类
    /// </summary>
    [TypeConverter(typeof(SlotPropertyConverter))]
    public class SlotProperty(SpineObject spine, string name)
    {
        private readonly SpineObject spine = spine;

        [Browsable(false)]
        public string Name { get; } = name;

        /// <summary>
        /// 实现了默认的转为字符串的方式
        /// </summary>
        public override string ToString() => spine.GetSlotAttachment(Name);

        /// <summary>
        /// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
        /// </summary>
        public override bool Equals(object? obj)
        {
            if (obj is SlotProperty) return ToString() == obj.ToString();
            return base.Equals(obj);
        }

        /// <summary>
        /// 哈希码需要和 Equals 行为类似
        /// </summary>
        public override int GetHashCode() => HashCode.Combine(typeof(SlotProperty).FullName.GetHashCode(), ToString().GetHashCode());
    }

    /// <summary>
    /// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
    /// </summary>
    public class SlotPropertyConverter : StringConverter
    {
        // NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
        // ToString 实现了 ConvertTo
        // SetValue 实现了从字符串设置属性

        public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;

        public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;

        public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
        {
            if (context?.PropertyDescriptor is PropertyDescriptor pd)
            {
                if (context?.Instance is SpineSlotProperty slots)
                {
                    if (slots.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
                        return new StandardValuesCollection(names);
                }
                else if (context?.Instance is object[] instances)
                {
                    IEnumerable<string> common = [];
                    foreach (SpineSlotProperty prop in instances.Where(inst => inst is SpineSlotProperty))
                    {
                        if (prop.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
                            common = common.Union(names);
                    }
                    return new StandardValuesCollection(common.ToArray());
                }
            }
            return base.GetStandardValues(context);
        }
    }
}
