using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CommunityToolkit.Mvvm.ComponentModel;
using NLog;
using Spine;
using Spine.Interfaces;
using SpineViewer.Extensions;

namespace SpineViewer.Models
{
    /// <summary>
    /// 线程安全的模型对象类
    /// </summary>
    public class SpineObjectModel : ObservableObject, SFML.Graphics.Drawable, IDisposable
    {
        /// <summary>
        /// 一些加载默认选项
        /// </summary>
        public static SpineObjectLoadOptions LoadOptions => _loadOptions;
        private static readonly SpineObjectLoadOptions _loadOptions = new();

        /// <summary>
        /// 日志器
        /// </summary>
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        private readonly object _lock = new();
        private readonly SpineObject _spineObject;

        private readonly ImmutableArray<string> _skins;
        private readonly FrozenDictionary<string, ImmutableArray<string>> _slotAttachments;
        private readonly ImmutableArray<string> _animations;

        /// <summary>
        /// 构造函数, 可能会抛出异常
        /// </summary>
        public SpineObjectModel(string skelPath, string? atlasPath = null)
        {
            _spineObject = new(skelPath, atlasPath)
            {
                UsePma = _loadOptions.UsePma,
                DebugTexture = _loadOptions.DebugTexture,
                DebugBounds = _loadOptions.DebugBounds,
                DebugRegions = _loadOptions.DebugRegions,
                DebugMeshHulls = _loadOptions.DebugMeshHulls,
                DebugMeshes = _loadOptions.DebugMeshes,
                DebugBoundingBoxes = _loadOptions.DebugBoundingBoxes,
                DebugPaths = _loadOptions.DebugPaths,
                DebugPoints = _loadOptions.DebugPoints,
                DebugClippings = _loadOptions.DebugClippings
            };
            _skins = _spineObject.Data.Skins.Select(v => v.Name).ToImmutableArray();
            _slotAttachments = _spineObject.Data.SlotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.Keys);
            _animations = _spineObject.Data.Animations.Select(v => v.Name).ToImmutableArray();

            // 默认自带一个动画
            if (_spineObject.Data.Animations.Length > 0)
                _spineObject.AnimationState.SetAnimation(0, _spineObject.Data.Animations[0], true);
        }

        /// <summary>
        /// 从工作区配置进行构造
        /// </summary>
        public SpineObjectModel(SpineObjectWorkspaceConfigModel cfg)
        {
            _spineObject = new(cfg.SkelPath, cfg.AtlasPath);
            _skins = _spineObject.Data.Skins.Select(v => v.Name).ToImmutableArray();
            _slotAttachments = _spineObject.Data.SlotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.Keys);
            _animations = _spineObject.Data.Animations.Select(v => v.Name).ToImmutableArray();
            ObjectConfig = cfg.ObjectConfig;
            _isShown = cfg.IsShown;
        }

        public event EventHandler<SkinStatusChangedEventArgs>? SkinStatusChanged;

        public event EventHandler<SlotVisibleChangedEventArgs>? SlotVisibleChanged;

        public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged;

        public event EventHandler<TrackPropertyChangedEventArgs>? TrackPropertyChanged;

        #region 参数面板实现

        public SpineVersion Version => _spineObject.Version;

        public string AssetsDir => _spineObject.AssetsDir;

        public string SkelPath => _spineObject.SkelPath;

        public string AtlasPath => _spineObject.AtlasPath;

        public string Name => _spineObject.Name;

        public string FileVersion => _spineObject.Data.SkeletonVersion;

        public bool IsSelected
        {
            get { lock (_lock) return _isSelected; }
            set { lock (_lock) SetProperty(ref _isSelected, value); }
        }
        private bool _isSelected = false;

        public bool IsShown
        {
            get { lock (_lock) return _isShown; }
            set { lock (_lock) SetProperty(ref _isShown, value); }
        }
        private bool _isShown = _loadOptions.IsShown;

        public bool UsePma
        {
            get { lock (_lock) return _spineObject.UsePma; }
            set { lock (_lock) SetProperty(_spineObject.UsePma, value, v => _spineObject.UsePma = v); }
        }

        public ISkeleton.Physics Physics
        {
            get { lock (_lock) return _spineObject.Physics; }
            set { lock (_lock) SetProperty(_spineObject.Physics, value, v => _spineObject.Physics = v); }
        }

        public float TimeScale
        {
            get { lock (_lock) return _spineObject.AnimationState.TimeScale; }
            set { lock (_lock) SetProperty(_spineObject.AnimationState.TimeScale, Math.Clamp(value, 0.01f, 100f), v => _spineObject.AnimationState.TimeScale = v); }
        }

        /// <summary>
        /// 缩放倍数, 绝对值大小, 两个方向大小不一致时返回 -1, 设置时不会影响正负号
        /// </summary>
        public float Scale
        {
            get
            {
                lock (_lock)
                {
                    var x = Math.Abs(_spineObject.Skeleton.ScaleX);
                    var y = Math.Abs(_spineObject.Skeleton.ScaleY);
                    return Math.Abs(x - y) < 1e-6 ? x : -1;
                }
            }
            set
            {
                lock (_lock)
                {
                    value = Math.Clamp(value, 0.001f, 1000f);
                    _spineObject.Skeleton.ScaleX = value * Math.Sign(_spineObject.Skeleton.ScaleX);
                    _spineObject.Skeleton.ScaleY = value * Math.Sign(_spineObject.Skeleton.ScaleY);
                    OnPropertyChanged();
                }
            }
        }

        /// <summary>
        /// 水平翻转
        /// </summary>
        public bool FlipX
        {
            get { lock (_lock) return _spineObject.Skeleton.ScaleX < 0; }
            set { lock (_lock) SetProperty(_spineObject.Skeleton.ScaleX < 0, value, v => _spineObject.Skeleton.ScaleX *= -1); }
        }

        /// <summary>
        /// 垂直翻转
        /// </summary>
        public bool FlipY
        {
            get { lock (_lock) return _spineObject.Skeleton.ScaleY < 0; }
            set { lock (_lock) SetProperty(_spineObject.Skeleton.ScaleY < 0, value, v => _spineObject.Skeleton.ScaleY *= -1); }
        }

        public float X
        {
            get { lock (_lock) return _spineObject.Skeleton.X; }
            set { lock (_lock) SetProperty(_spineObject.Skeleton.X, value, v => _spineObject.Skeleton.X = v); }
        }

        public float Y
        {
            get { lock (_lock) return _spineObject.Skeleton.Y; }
            set { lock (_lock) SetProperty(_spineObject.Skeleton.Y, value, v => _spineObject.Skeleton.Y = v); }
        }

        public ImmutableArray<string> Skins => _skins;

        public bool GetSkinStatus(string skinName)
        {
            lock (_lock) return _spineObject.GetSkinStatus(skinName);
        }

        public bool SetSkinStatus(string skinName, bool status)
        {
            bool changed = false;
            lock (_lock) changed = _spineObject.SetSkinStatus(skinName, status);
            if (changed) SkinStatusChanged?.Invoke(this, new(skinName, status));
            return changed;
        }

        public FrozenDictionary<string, ImmutableArray<string>> SlotAttachments => _slotAttachments;

        public bool GetSlotVisible(string slotName)
        {
            lock (_lock) return _spineObject.GetSlotVisible(slotName);
        }

        public bool SetSlotVisible(string slotName, bool visible)
        {
            bool changed = false;
            lock (_lock) changed = _spineObject.SetSlotVisible(slotName, visible);
            if (changed) SlotVisibleChanged?.Invoke(this, new(slotName, visible));
            return changed;
        }

        public string? GetAttachment(string slotName)
        {
            lock (_lock) return _spineObject.GetAttachment(slotName);
        }

        public bool SetAttachment(string slotName, string? attachmentName)
        {
            bool changed = false;
            lock (_lock) changed = _spineObject.SetAttachment(slotName, attachmentName);
            if (changed) SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
            return changed;
        }

        public ImmutableArray<string> Animations => _animations;

        public float GetAnimationDuration(string name)
        {
            if (_spineObject.Data.AnimationsByName.TryGetValue(name, out var ani))
                return ani.Duration;
            return 0f;
        }

        public string? GetAnimation(int index)
        {
            lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.Animation.Name;
        }

        /// <summary>
        /// 在指定轨道上设置循环动画, 会忽略不存在的动画
        /// </summary>
        public void SetAnimation(int index, string name)
        {
            bool changed = false;
            float lastTimeScale = 1f;
            float lastAlpha = 1f;
            lock (_lock)
            {
                if (_spineObject.Data.AnimationsByName.ContainsKey(name))
                {
                    // 需要记录之前的轨道属性值并还原
                    if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
                    {
                        lastTimeScale = entry.TimeScale;
                        lastAlpha = entry.Alpha;
                    }
                    entry = _spineObject.AnimationState.SetAnimation(index, name, true);
                    entry.TimeScale = lastTimeScale;
                    entry.Alpha = lastAlpha;

                    // XXX(#105): 部分 3.4.02 版本模型在设置动画后出现附件残留, 因此强制进行一次 Setup
                    if (_spineObject.Version == SpineVersion.V34)
                    {
                        _spineObject.Skeleton.SetSlotsToSetupPose();
                    }
                    changed = true;
                }
            }
            if (changed) TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
        }

        public float GetTrackTimeScale(int index)
        {
            lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.TimeScale ?? 1;
        }

        public void SetTrackTimeScale(int index, float scale)
        {
            lock (_lock)
            {
                if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
                {
                    entry.TimeScale = Math.Clamp(scale, 0.01f, 100f);
                    TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.TimeScale)));
                }
            }
        }

        public float GetTrackAlpha(int index)
        {
            lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.Alpha ?? 1;
        }

        public void SetTrackAlpha(int index, float alpha)
        {
            lock (_lock)
            {
                if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
                {
                    entry.Alpha = Math.Clamp(alpha, 0f, 1f);
                    TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.Alpha)));
                }
            }
        }

        public int[] GetTrackIndices()
        {
            lock (_lock)
            {
                List<int> indices = [];
                int idx = 0;
                foreach (var e in _spineObject.AnimationState.IterTracks())
                {
                    if (e is not null) indices.Add(idx);
                    idx++;
                }
                return indices.ToArray();
            }
        }

        public void ClearTrack(int index)
        {
            lock (_lock) _spineObject.AnimationState.ClearTrack(index);
            TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
        }

        public void ResetAnimationsTime()
        {
            lock (_lock) _spineObject.ResetAnimationsTime();
        }

        public bool EnableDebug
        {
            get { lock (_lock) return _spineObject.EnableDebug; }
            set { lock (_lock) SetProperty(_spineObject.EnableDebug, value, v => _spineObject.EnableDebug = v); }
        }

        public bool DebugTexture
        {
            get { lock (_lock) return _spineObject.DebugTexture; }
            set { lock (_lock) SetProperty(_spineObject.DebugTexture, value, v => _spineObject.DebugTexture = v); }
        }

        public bool DebugBounds
        {
            get { lock (_lock) return _spineObject.DebugBounds; }
            set { lock (_lock) SetProperty(_spineObject.DebugBounds, value, v => _spineObject.DebugBounds = v); }
        }

        public bool DebugBones
        {
            get { lock (_lock) return _spineObject.DebugBones; }
            set { lock (_lock) SetProperty(_spineObject.DebugBones, value, v => _spineObject.DebugBones = v); }
        }

        public bool DebugRegions
        {
            get { lock (_lock) return _spineObject.DebugRegions; }
            set { lock (_lock) SetProperty(_spineObject.DebugRegions, value, v => _spineObject.DebugRegions = v); }
        }

        public bool DebugMeshHulls
        {
            get { lock (_lock) return _spineObject.DebugMeshHulls; }
            set { lock (_lock) SetProperty(_spineObject.DebugMeshHulls, value, v => _spineObject.DebugMeshHulls = v); }
        }

        public bool DebugMeshes
        {
            get { lock (_lock) return _spineObject.DebugMeshes; }
            set { lock (_lock) SetProperty(_spineObject.DebugMeshes, value, v => _spineObject.DebugMeshes = v); }
        }

        public bool DebugBoundingBoxes
        {
            get { lock (_lock) return _spineObject.DebugBoundingBoxes; }
            set { lock (_lock) SetProperty(_spineObject.DebugBoundingBoxes, value, v => _spineObject.DebugBoundingBoxes = v); }
        }

        public bool DebugPaths
        {
            get { lock (_lock) return _spineObject.DebugPaths; }
            set { lock (_lock) SetProperty(_spineObject.DebugPaths, value, v => _spineObject.DebugPaths = v); }
        }

        public bool DebugPoints
        {
            get { lock (_lock) return _spineObject.DebugPoints; }
            set { lock (_lock) SetProperty(_spineObject.DebugPoints, value, v => _spineObject.DebugPoints = v); }
        }

        public bool DebugClippings
        {
            get { lock (_lock) return _spineObject.DebugClippings; }
            set { lock (_lock) SetProperty(_spineObject.DebugClippings, value, v => _spineObject.DebugClippings = v); }
        }

        #endregion

        public void Update(float delta)
        {
            lock (_lock) _spineObject.Update(delta);
        }

        /// <summary>
        /// 获取一个独立的内部对象, 继承所有状态
        /// </summary>
        public SpineObject GetSpineObject(bool keepTrackTime = false)
        {
            lock (_lock) return _spineObject.Copy(keepTrackTime);
        }

        /// <summary>
        /// 获取当前状态包围盒
        /// </summary>
        public Rect GetCurrentBounds()
        {
            lock (_lock) return _spineObject.GetCurrentBounds();
        }

        /// <summary>
        /// 命中检测
        /// </summary>
        public bool HitTest(float x, float y)
        {
            lock (_lock) return _spineObject.Skeleton.HitTest(x, y);
        }

        public SpineObjectConfigModel ObjectConfig
        {
            get
            {
                lock (_lock)
                {
                    SpineObjectConfigModel config = new()
                    {
                        Scale = Math.Abs(_spineObject.Skeleton.ScaleX),
                        FlipX = _spineObject.Skeleton.ScaleX < 0,
                        FlipY = _spineObject.Skeleton.ScaleY < 0,
                        X = _spineObject.Skeleton.X,
                        Y = _spineObject.Skeleton.Y,

                        UsePma = _spineObject.UsePma,
                        Physics = _spineObject.Physics.ToString(),
                        TimeScale = _spineObject.AnimationState.TimeScale,

                        DebugTexture = _spineObject.DebugTexture,
                        DebugBounds = _spineObject.DebugBounds,
                        DebugBones = _spineObject.DebugBones,
                        DebugRegions = _spineObject.DebugRegions,
                        DebugMeshHulls = _spineObject.DebugMeshHulls,
                        DebugMeshes = _spineObject.DebugMeshes,
                        DebugBoundingBoxes = _spineObject.DebugBoundingBoxes,
                        DebugPaths = _spineObject.DebugPaths,
                        DebugPoints = _spineObject.DebugPoints,
                        DebugClippings = _spineObject.DebugClippings
                    };

                    config.LoadedSkins.AddRange(_spineObject.Data.Skins.Select(it => it.Name).Where(_spineObject.GetSkinStatus));

                    foreach (var slot in _spineObject.Skeleton.Slots) config.SlotAttachment[slot.Name] = slot.Attachment?.Name;

                    config.DisabledSlots = _spineObject.Skeleton.Slots.Where(it => it.Disabled).Select(it => it.Name).ToList();

                    // XXX: 处理空动画
                    foreach (var tr in _spineObject.AnimationState.IterTracks())
                    {
                        if (tr is not null)
                        {
                            config.Animations.Add(new()
                            {
                                AnimationName = tr.Animation.Name,
                                TimeScale = tr.TimeScale,
                                Alpha = tr.Alpha
                            });
                        }
                        else
                        {
                            config.Animations.Add(null);
                        }
                    }

                    return config;
                }
            }
            set => ApplyObjectConfig(value);
        }

        public void ApplyObjectConfig(SpineObjectConfigModel m, SpineObjectConfigApplyFlag flag = SpineObjectConfigApplyFlag.All)
        {
            lock (_lock)
            {
                if (flag == SpineObjectConfigApplyFlag.All)
                {
                    _spineObject.Skeleton.ScaleX = m.Scale;
                    _spineObject.Skeleton.ScaleY = m.Scale;
                    OnPropertyChanged(nameof(Scale));
                    SetProperty(_spineObject.Skeleton.ScaleX < 0, m.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX));
                    SetProperty(_spineObject.Skeleton.ScaleY < 0, m.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY));
                    SetProperty(_spineObject.Skeleton.X, m.X, v => _spineObject.Skeleton.X = v, nameof(X));
                    SetProperty(_spineObject.Skeleton.Y, m.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
                    SetProperty(_spineObject.UsePma, m.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
                    SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(m.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
                    SetProperty(_spineObject.AnimationState.TimeScale, m.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale));
                }

                if (flag == SpineObjectConfigApplyFlag.All || flag == SpineObjectConfigApplyFlag.Skin)
                {
                    foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(m.LoadedSkins))
                        if (_spineObject.SetSkinStatus(name, false))
                            SkinStatusChanged?.Invoke(this, new(name, false));
                    foreach (var name in m.LoadedSkins)
                        if (_spineObject.SetSkinStatus(name, true))
                            SkinStatusChanged?.Invoke(this, new(name, true));
                }

                if (flag == SpineObjectConfigApplyFlag.SlotAttachement)
                {
                    foreach (var (slotName, attachmentName) in m.SlotAttachment)
                        if (_spineObject.SetAttachment(slotName, attachmentName))
                            SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
                }

                if (flag == SpineObjectConfigApplyFlag.SlotVisibility)
                {
                    foreach (var slotName in m.DisabledSlots)
                        if (_spineObject.SetSlotVisible(slotName, false))
                            SlotVisibleChanged?.Invoke(this, new(slotName, false));
                }

                if (flag == SpineObjectConfigApplyFlag.All)
                {
                    // XXX: 处理空动画
                    _spineObject.AnimationState.ClearTracks();
                    int trackIndex = 0;
                    foreach (var trConfig in m.Animations)
                    {
                        if (trConfig is not null && !string.IsNullOrEmpty(trConfig.AnimationName))
                        {
                            var tr = _spineObject.AnimationState.SetAnimation(trackIndex, trConfig.AnimationName, true);
                            tr.TimeScale = trConfig.TimeScale;
                            tr.Alpha = trConfig.Alpha;
                            TrackPropertyChanged?.Invoke(this, new(trackIndex, nameof(TrackPropertyChangedEventArgs.AnimationName)));
                        }
                        trackIndex++;
                    }

                    // XXX(#105): 部分 3.4.02 版本模型在设置动画后出现附件残留, 因此强制进行一次 Setup
                    if (_spineObject.Version == SpineVersion.V34)
                    {
                        _spineObject.Skeleton.SetSlotsToSetupPose();
                    }

                    SetProperty(_spineObject.DebugTexture, m.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture));
                    SetProperty(_spineObject.DebugBounds, m.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds));
                    SetProperty(_spineObject.DebugBones, m.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones));
                    SetProperty(_spineObject.DebugRegions, m.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions));
                    SetProperty(_spineObject.DebugMeshHulls, m.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls));
                    SetProperty(_spineObject.DebugMeshes, m.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes));
                    SetProperty(_spineObject.DebugBoundingBoxes, m.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes));
                    SetProperty(_spineObject.DebugPaths, m.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths));
                    SetProperty(_spineObject.DebugPoints, m.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints));
                    SetProperty(_spineObject.DebugClippings, m.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings));
                }
            }
        }

        public SpineObjectWorkspaceConfigModel WorkspaceConfig
        {
            get
            {
                return new()
                {
                    SkelPath = SkelPath,
                    AtlasPath = AtlasPath,
                    IsShown = IsShown,
                    ObjectConfig = ObjectConfig
                };
            }
        }

        #region SFML.Graphics.Drawable 接口实现

        public void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
        {
            lock (_lock) _spineObject.Draw(target, states);
        }

        #endregion

        #region IDisposable 接口实现

        private bool _disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            if (disposing)
            {
                _spineObject.Dispose();
            }
            _disposed = true;
        }

        ~SpineObjectModel()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            if (_disposed)
            {
                GC.SuppressFinalize(this);
            }
        }

        #endregion
    }

    /// <summary>
    /// 可选的应用部分模型参数项
    /// </summary>
    public enum SpineObjectConfigApplyFlag
    {
        All,
        Skin,
        SlotAttachement,
        SlotVisibility,
    }

    public class SkinStatusChangedEventArgs(string name, bool status) : EventArgs
    {
        public string Name { get; } = name;
        public bool Status { get; } = status;
    }

    public class SlotVisibleChangedEventArgs(string slotName, bool visible) : EventArgs
    {
        public string SlotName { get; } = slotName;
        public bool Visible { get; } = visible;
    }

    public class SlotAttachmentChangedEventArgs(string slotName, string? attachmentName) : EventArgs
    {
        public string SlotName { get; } = slotName;
        public string? AttachmentName { get; } = attachmentName;
    }

    /// <summary>
    /// 模型动画轨道属性变化事件参数, 需要检索 <c><see cref="PropertyName"/></c> 来确定发生变化的属性是什么
    /// </summary>
    /// <param name="trackIndex">发生属性变化的轨道索引</param>
    /// <param name="propertyName">使用 <c>nameof</c> 设置发生改变的属性名</param>
    public class TrackPropertyChangedEventArgs(int trackIndex, string propertyName) : EventArgs
    {
        public int TrackIndex { get; } = trackIndex;

        /// <summary>
        /// 发生变化的属性名, 将会使用 <c>nameof</c> 设置为属性名称字符串
        /// </summary>
        public string PropertyName { get; } = propertyName;

        public string? AnimationName { get; }
        public float TimeScale { get; } = 1f;
        public float Alpha { get; } = 1f;
    }

    public class SpineObjectLoadOptions
    {
        public bool IsShown { get; set; } = true;
        public bool UsePma { get; set; }
        public bool DebugTexture { get; set; } = true;
        public bool DebugBounds { get; set; }
        public bool DebugBones { get; set; }
        public bool DebugRegions { get; set; }
        public bool DebugMeshHulls { get; set; }
        public bool DebugMeshes { get; set; }
        public bool DebugBoundingBoxes { get; set; }
        public bool DebugPaths { get; set; }
        public bool DebugPoints { get; set; }
        public bool DebugClippings { get; set; }
    }
}
