using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NLog;
using Spine;
using SpineViewer.Extensions;
using SpineViewer.Models;
using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.Utils;
using SpineViewer.ViewModels.Exporters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Shell;

namespace SpineViewer.ViewModels.MainWindow
{
    public class SpineObjectListViewModel : ObservableObject
    {
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// 主窗口视图模型引用
        /// </summary>
        private readonly MainWindowViewModel _vmMain;

        /// <summary>
        /// 临时对象, 存储复制的模型参数
        /// </summary>
        private SpineObjectConfigModel? _copiedSpineObjectConfigModel = null;
        private SpineObjectConfigApplyFlag _copiedConfigFlag = SpineObjectConfigApplyFlag.All;

        public SpineObjectListViewModel(MainWindowViewModel mainViewModel)
        {
            _vmMain = mainViewModel;
            _spineObjectModels = _vmMain.SpineObjects; // 缓存对象

            _frameExporterViewModel = new(_vmMain);
            _frameSequenceExporterViewModel = new(_vmMain);
            _ffmpegVideoExporterViewModel = new(_vmMain);
            _customFFmpegExporterViewModel = new(_vmMain);
        }

        /// <summary>
        /// 请求选中项发生变化
        /// </summary>
        public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;

        /// <summary>
        /// 单帧导出 ViewModel
        /// </summary>
        public FrameExporterViewModel FrameExporterViewModel => _frameExporterViewModel;
        private readonly FrameExporterViewModel _frameExporterViewModel;

        /// <summary>
        /// 帧序列 ViewModel
        /// </summary>
        public FrameSequenceExporterViewModel FrameSequenceExporterViewModel => _frameSequenceExporterViewModel;
        private readonly FrameSequenceExporterViewModel _frameSequenceExporterViewModel;

        /// <summary>
        /// 动图/视频 ViewModel
        /// </summary>
        public FFmpegVideoExporterViewModel FFmpegVideoExporterViewModel => _ffmpegVideoExporterViewModel;
        private readonly FFmpegVideoExporterViewModel _ffmpegVideoExporterViewModel;

        /// <summary>
        /// 动图/视频 ViewModel
        /// </summary>
        public CustomFFmpegExporterViewModel CustomFFmpegExporterViewModel => _customFFmpegExporterViewModel;
        private readonly CustomFFmpegExporterViewModel _customFFmpegExporterViewModel;

        /// <summary>
        /// 已加载的 Spine 对象
        /// </summary>
        public ObservableCollectionWithLock<SpineObjectModel> SpineObjects => _spineObjectModels;
        private readonly ObservableCollectionWithLock<SpineObjectModel> _spineObjectModels;

        /// <summary>
        /// 列表视图选中项发生改变时同步内部模型列表状态
        /// </summary>
        public RelayCommand<IList?> Cmd_ListViewSelectionChanged => _cmd_ListViewSelectionChanged ??= new(ListViewSelectionChanged_Execute);
        private RelayCommand<IList?>? _cmd_ListViewSelectionChanged;

        private void ListViewSelectionChanged_Execute(IList? args)
        {
            if (args is null) return;
            lock (_spineObjectModels.Lock)
            {
                var selectedItems = args.Cast<SpineObjectModel>().ToArray();
                foreach (var it in _spineObjectModels.Except(selectedItems)) it.IsSelected = false;
                foreach (var it in selectedItems) it.IsSelected = true;
                _vmMain.SpineObjectTabViewModel.SelectedObjects = selectedItems;
            }
        }

        /// <summary>
        /// 从路径列表添加对象
        /// </summary>
        /// <param name="paths">可以是文件和文件夹</param>
        public void AddSpineObjectFromFileList(IEnumerable<string> paths)
        {
            List<string> validPaths = [];
            foreach (var path in paths)
            {
                if (File.Exists(path))
                {
                    var lowerPath = path.ToLowerInvariant();
                    if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
                        validPaths.Add(path);
                }
                else if (Directory.Exists(path))
                {
                    foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
                    {
                        var lowerPath = file.ToLowerInvariant();
                        if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith))
                            validPaths.Add(file);
                    }
                }
            }

            if (validPaths.Count > 1)
            {
                if (validPaths.Count > 100)
                {
                    if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count)))
                        return;
                }
                ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask(
                    validPaths.ToArray(), pr, ct),
                    AppResource.Str_AddSpineObjectsTitle
                );
            }
            else if (validPaths.Count > 0)
            {
                InsertSpineObject(validPaths[0]);
                _logger.LogCurrentProcessMemoryUsage();
            }
        }

        /// <summary>
        /// 用于后台添加模型的任务方法
        /// </summary>
        private void AddSpineObjectsTask(string[] paths, IProgressReporter reporter, CancellationToken ct)
        {
            int totalCount = paths.Length;
            int success = 0;
            int error = 0;

            _vmMain.ProgressState = TaskbarItemProgressState.Normal;
            _vmMain.ProgressValue = 0;

            reporter.Total = totalCount;
            reporter.Done = 0;
            reporter.ProgressText = $"[0/{totalCount}]";
            for (int i = 0; i < totalCount; i++)
            {
                if (ct.IsCancellationRequested) break;

                var skelPath = paths[i];
                reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";

                if (InsertSpineObject(skelPath))
                    success++;
                else
                    error++;

                reporter.Done = i + 1;
                reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}";
                _vmMain.ProgressValue = (i + 1f) / totalCount;
            }
            _vmMain.ProgressState = TaskbarItemProgressState.None;

            if (error > 0)
                _logger.Warn("Batch load {0} successfully, {1} failed", success, error);
            else
                _logger.Info("{0} skel loaded successfully", success);

            _logger.LogCurrentProcessMemoryUsage();
        }

        /// <summary>
        /// 安全地在列表头添加一个模型, 发生错误会输出日志
        /// </summary>
        /// <returns>是否添加成功</returns>
        private bool InsertSpineObject(string skelPath, string? atlasPath = null)
        {
            try
            {
                var sp = new SpineObjectModel(skelPath, atlasPath);
                lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
                if (Application.Current.Dispatcher.CheckAccess())
                {
                    RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                    RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
                }
                else
                {
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
                    });
                }
                return true;
            }
            catch (Exception ex)
            {
                _logger.Trace(ex.ToString());
                _logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
            }
            return false;
        }

        #region 模型列表管理菜单项实现

        /// <summary>
        /// 弹窗添加单模型命令
        /// </summary>
        public RelayCommand Cmd_AddSpineObject => _cmd_AddSpineObject ??= new(AddSpineObject_Execute);
        private RelayCommand? _cmd_AddSpineObject;

        private void AddSpineObject_Execute()
        {
            if (!DialogService.ShowOpenFileDialog(out var skelFileName, AppResource.Str_OpenSkelFileTitle))
                return;
            if (!DialogService.ShowOpenFileDialog(out var atlasFileName, AppResource.Str_OpenAtlasFileTitle))
                return;
            InsertSpineObject(skelFileName, atlasFileName);
            _logger.LogCurrentProcessMemoryUsage();
        }

        /// <summary>
        /// 移除给定模型
        /// </summary>
        public RelayCommand<IList?> Cmd_RemoveSpineObject => _cmd_RemoveSpineObject ??= new(RemoveSpineObject_Execute, RemoveSpineObject_CanExecute);
        private RelayCommand<IList?>? _cmd_RemoveSpineObject;

        private void RemoveSpineObject_Execute(IList? args)
        {
            if (!RemoveSpineObject_CanExecute(args)) return;

            if (args.Count > 1)
            {
                if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_RemoveItemsQuest, args.Count)))
                    return;
            }

            lock (_spineObjectModels.Lock)
            {
                // NOTE: 这里必须要浅拷贝一次, 不能直接对会被修改的绑定数据 args 进行 foreach 遍历
                foreach (var sp in args.Cast<SpineObjectModel>().ToArray())
                {
                    _spineObjectModels.Remove(sp);
                    sp.Dispose();
                }
            }
        }

        private bool RemoveSpineObject_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count <= 0) return false;
            return true;
        }

        /// <summary>
        /// 移除全部模型
        /// </summary>
        public RelayCommand<IList?> Cmd_RemoveAllSpineObject => _cmd_RemoveAllSpineObject ??= new(RemoveAllSpineObject_Execute, RemoveAllSpineObject_CanExecute);
        private RelayCommand<IList?>? _cmd_RemoveAllSpineObject;

        private void RemoveAllSpineObject_Execute(IList? args)
        {
            if (!RemoveAllSpineObject_CanExecute(args)) return;

            if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_RemoveItemsQuest, args.Count)))
                return;

            lock (_spineObjectModels.Lock)
            {
                foreach (var sp in _spineObjectModels)
                    sp.Dispose();
                _spineObjectModels.Clear();
            }
        }

        private bool RemoveAllSpineObject_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count <= 0) return false;
            return true;
        }

        /// <summary>
        /// 从剪贴板文件列表添加模型
        /// </summary>
        public RelayCommand Cmd_AddSpineObjectFromClipboard => _cmd_AddSpineObjectFromClipboard ??= new(AddSpineObjectFromClipboard_Execute);
        private RelayCommand? _cmd_AddSpineObjectFromClipboard;

        private void AddSpineObjectFromClipboard_Execute()
        {
            if (!Clipboard.ContainsFileDropList()) return;
            AddSpineObjectFromFileList(Clipboard.GetFileDropList().Cast<string>().ToArray());
        }

        /// <summary>
        /// 重新加载模型
        /// </summary>
        public RelayCommand<IList?> Cmd_ReloadSpineObject => _cmd_ReloadSpineObject ??= new(ReloadSpineObject_Execute, ReloadSpineObject_CanExecute);
        private RelayCommand<IList?>? _cmd_ReloadSpineObject;

        private void ReloadSpineObject_Execute(IList? args)
        {
            if (!ReloadSpineObject_CanExecute(args)) return;

            if (args.Count <= 1)
            {
                lock (_spineObjectModels.Lock)
                {
                    var sp = (SpineObjectModel)args[0];
                    var idx = _spineObjectModels.IndexOf(sp);
                    if (idx < 0) return;

                    try
                    {
                        var spNew = new SpineObjectModel(sp.SkelPath, sp.AtlasPath);
                        spNew.ObjectConfig = sp.ObjectConfig;
                        _spineObjectModels[idx] = spNew;
                        sp.Dispose();
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
                    }
                    catch (Exception ex)
                    {
                        _logger.Trace(ex.ToString());
                        _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
                    }
                }
            }
            else
            {
                ProgressService.RunAsync((pr, ct) => ReloadSpineObjectsTask(
                    args.Cast<SpineObjectModel>().ToArray(), pr, ct),
                    AppResource.Str_ReloadSpineObjectsTitle
                );
            }
        }

        private bool ReloadSpineObject_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count <= 0) return false;
            return true;
        }

        private void ReloadSpineObjectsTask(SpineObjectModel[] spines, IProgressReporter reporter, CancellationToken ct)
        {
            int totalCount = spines.Length;
            int success = 0;
            int error = 0;

            _vmMain.ProgressState = TaskbarItemProgressState.Normal;
            _vmMain.ProgressValue = 0;

            reporter.Total = totalCount;
            reporter.Done = 0;
            reporter.ProgressText = $"[0/{totalCount}]";
            for (int i = 0; i < totalCount; i++)
            {
                if (ct.IsCancellationRequested) break;

                var sp = spines[i];
                reporter.ProgressText = $"[{i}/{totalCount}] {sp.Name}";

                lock (_spineObjectModels.Lock)
                {
                    var idx = _spineObjectModels.IndexOf(sp);
                    if (idx >= 0)
                    {
                        try
                        {
                            var spNew = new SpineObjectModel(sp.SkelPath, sp.AtlasPath);
                            spNew.ObjectConfig = sp.ObjectConfig;
                            _spineObjectModels[idx] = spNew;
                            sp.Dispose();
                            success++;
                            Application.Current.Dispatcher.BeginInvoke(() =>
                            {
                                RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                                RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
                            });
                        }
                        catch (Exception ex)
                        {
                            error++;
                            _logger.Trace(ex.ToString());
                            _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
                        }
                    }
                }

                reporter.Done = i + 1;
                reporter.ProgressText = $"[{i + 1}/{totalCount}] {sp.Name}";
                _vmMain.ProgressValue = (i + 1f) / totalCount;
            }
            _vmMain.ProgressState = TaskbarItemProgressState.None;

            if (error > 0)
                _logger.Warn("Batch reload {0} successfully, {1} failed", success, error);
            else
                _logger.Info("{0} skel reloaded successfully", success);

            _logger.LogCurrentProcessMemoryUsage();
        }

        /// <summary>
        /// 模型上移一位
        /// </summary>
        public RelayCommand<IList?> Cmd_MoveUpSpineObject => _cmd_MoveUpSpineObject ??= new(MoveUpSpineObject_Execute, MoveUpSpineObject_CanExecute);
        private RelayCommand<IList?>? _cmd_MoveUpSpineObject;

        private void MoveUpSpineObject_Execute(IList? args)
        {
            if (!MoveUpSpineObject_CanExecute(args)) return;
            var sp = (SpineObjectModel)args[0];
            lock (_spineObjectModels.Lock)
            {
                var idx = _spineObjectModels.IndexOf(sp);
                if (idx <= 0) return;
                _spineObjectModels.Move(idx, idx - 1);
            }
        }

        private bool MoveUpSpineObject_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count != 1) return false;
            return true;
        }

        /// <summary>
        /// 模型下移一位
        /// </summary>
        public RelayCommand<IList?> Cmd_MoveDownSpineObject => _cmd_MoveDownSpineObject ??= new(MoveDownSpineObject_Execute, MoveDownSpineObject_CanExecute);
        private RelayCommand<IList?>? _cmd_MoveDownSpineObject;

        private void MoveDownSpineObject_Execute(IList? args)
        {
            if (!MoveDownSpineObject_CanExecute(args)) return;
            var sp = (SpineObjectModel)args[0];
            lock (_spineObjectModels.Lock)
            {
                var idx = _spineObjectModels.IndexOf(sp);
                if (idx < 0 || idx >= _spineObjectModels.Count - 1) return;
                _spineObjectModels.Move(idx, idx + 1);
            }
        }

        private bool MoveDownSpineObject_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count != 1) return false;
            return true;
        }

        #endregion

        #region 模型参数管理菜单项实现

        /// <summary>
        /// 复制模型参数
        /// </summary>
        public RelayCommand<IList?> Cmd_CopySpineObjectConfig => _cmd_CopySpineObjectConfig ??= new(
            args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.All), 
            CopySpineObjectConfig_CanExecute
        );
        private RelayCommand<IList?>? _cmd_CopySpineObjectConfig;

        /// <summary>
        /// 复制模型参数 (仅皮肤)
        /// </summary>
        public RelayCommand<IList?> Cmd_CopySpineObjectSkinConfig => _cmd_CopySpineObjectSkinConfig ??= new(
            args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.Skin), 
            CopySpineObjectConfig_CanExecute
        );
        private RelayCommand<IList?>? _cmd_CopySpineObjectSkinConfig;

        /// <summary>
        /// 复制模型参数 (仅插槽附件)
        /// </summary>
        public RelayCommand<IList?> Cmd_CopySpineObjectSlotAttachmentConfig => _cmd_CopySpineObjectSlotAttachmentConfig ??= new(
            args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotAttachement), 
            CopySpineObjectConfig_CanExecute
        );
        private RelayCommand<IList?>? _cmd_CopySpineObjectSlotAttachmentConfig;

        /// <summary>
        /// 复制模型参数 (仅插槽可见性)
        /// </summary>
        public RelayCommand<IList?> Cmd_CopySpineObjectSlotVisibilityConfig => _cmd_CopySpineObjectSlotVisibilityConfig ??= new(
            args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotVisibility), 
            CopySpineObjectConfig_CanExecute
        );
        private RelayCommand<IList?>? _cmd_CopySpineObjectSlotVisibilityConfig;

        private void CopySpineObjectConfig_Execute(IList? args, SpineObjectConfigApplyFlag flag)
        {
            if (!CopySpineObjectConfig_CanExecute(args)) return;
            var sp = (SpineObjectModel)args[0];
            _copiedSpineObjectConfigModel = sp.ObjectConfig;
            _copiedConfigFlag = flag;
            _logger.Info("Copy config[{0}] from model: {1}", flag, sp.Name);
        }

        private bool CopySpineObjectConfig_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count != 1) return false;
            return true;
        }

        /// <summary>
        /// 应用复制的模型参数
        /// </summary>
        public RelayCommand<IList?> Cmd_ApplySpineObjectConfig => _cmd_ApplySpineObjectConfig ??= new(ApplySpineObjectConfig_Execute, ApplySpineObjectConfig_CanExecute);
        private RelayCommand<IList?>? _cmd_ApplySpineObjectConfig;

        private void ApplySpineObjectConfig_Execute(IList? args)
        {
            if (!ApplySpineObjectConfig_CanExecute(args)) return;
            foreach (SpineObjectModel sp in args)
            {
                sp.ApplyObjectConfig(_copiedSpineObjectConfigModel, _copiedConfigFlag);
                _logger.Info("Apply config[{0}] to model: {1}", _copiedConfigFlag, sp.Name);
            }
        }

        private bool ApplySpineObjectConfig_CanExecute(IList? args)
        {
            if (_copiedSpineObjectConfigModel is null) return false;
            if (args is null) return false;
            if (args.Count <= 0) return false;
            return true;
        }

        public RelayCommand<IList?> Cmd_ApplySpineObjectConfigFromFile => _cmd_ApplySpineObjectConfigFromFile ??= new(ApplySpineObjectConfigFromFile_Execute, ApplySpineObjectConfigFromFile_CanExecute);
        private RelayCommand<IList?>? _cmd_ApplySpineObjectConfigFromFile;

        private void ApplySpineObjectConfigFromFile_Execute(IList? args)
        {
            if (!ApplySpineObjectConfigFromFile_CanExecute(args)) return;
            if (!DialogService.ShowOpenJsonDialog(out var fileName)) return;
            if (JsonHelper.Deserialize<SpineObjectConfigModel>(fileName, out var config))
            {
                foreach (SpineObjectModel sp in args)
                {
                    sp.ObjectConfig = config;
                    _logger.Info("Apply config to model: {0}", sp.Name);
                }
            }
        }

        private bool ApplySpineObjectConfigFromFile_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count <= 0) return false;
            return true;
        }

        public RelayCommand<IList?> Cmd_SaveSpineObjectConfigToFile => _cmd_SaveSpineObjectConfigToFile ??= new(SaveSpineObjectConfigToFile_Execute, SaveSpineObjectConfigToFile_CanExecute);
        private RelayCommand<IList?>? _cmd_SaveSpineObjectConfigToFile;

        private void SaveSpineObjectConfigToFile_Execute(IList? args)
        {
            if (!SaveSpineObjectConfigToFile_CanExecute(args)) return;
            var sp = (SpineObjectModel)args[0];
            var config = sp.ObjectConfig;

            string fileName = $"{Path.ChangeExtension(Path.GetFileName(sp.SkelPath), ".jcfg")}";
            if (!DialogService.ShowSaveJsonDialog(ref fileName, sp.AssetsDir)) return;
            JsonHelper.Serialize(sp.ObjectConfig, fileName);
        }

        private bool SaveSpineObjectConfigToFile_CanExecute(IList? args)
        {
            if (args is null) return false;
            if (args.Count != 1) return false;
            return true;
        }

        #endregion

        #region 工作区参数实现

        public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects
        {
            get
            {
                List<SpineObjectWorkspaceConfigModel> loadedSpineObjects = [];
                lock (_spineObjectModels.Lock)
                {
                    foreach (var sp in _spineObjectModels)
                    {
                        loadedSpineObjects.Add(sp.WorkspaceConfig);
                    }
                }
                return loadedSpineObjects;
            }
            set
            {
                AddSpineObjectFromWorkspaceList(value);
            }
        }

        private void AddSpineObjectFromWorkspaceList(List<SpineObjectWorkspaceConfigModel> models)
        {
            lock (_spineObjectModels.Lock)
            {
                var spines = _spineObjectModels.ToArray();
                _spineObjectModels.Clear();
                foreach (var sp in spines)
                {
                    sp.Dispose();
                }
            }

            if (models.Count > 1)
            {
                ProgressService.RunAsync((pr, ct) => AddSpineObjectFromWorkspaceListTask(
                    models, pr, ct),
                    AppResource.Str_AddSpineObjectsTitle
                );
            }
            else if (models.Count > 0)
            {
                InsertSpineObject(models[0]);
                _logger.LogCurrentProcessMemoryUsage();
            }
        }

        private void AddSpineObjectFromWorkspaceListTask(List<SpineObjectWorkspaceConfigModel> models, IProgressReporter reporter, CancellationToken ct)
        {
            int totalCount = models.Count;
            int success = 0;
            int error = 0;

            _vmMain.ProgressState = TaskbarItemProgressState.Normal;
            _vmMain.ProgressValue = 0;

            reporter.Total = totalCount;
            reporter.Done = 0;
            reporter.ProgressText = $"[0/{totalCount}]";
            for (int i = 0; i < totalCount; i++)
            {
                if (ct.IsCancellationRequested) break;

                var cfg = models[totalCount - 1 - i];
                reporter.ProgressText = $"[{i}/{totalCount}] {cfg}";

                if (InsertSpineObject(cfg))
                    success++;
                else
                    error++;

                reporter.Done = i + 1;
                reporter.ProgressText = $"[{i + 1}/{totalCount}] {cfg}";
                _vmMain.ProgressValue = (i + 1f) / totalCount;
            }
            _vmMain.ProgressState = TaskbarItemProgressState.None;

            if (error > 0)
                _logger.Warn("Batch load {0} successfully, {1} failed", success, error);
            else
                _logger.Info("{0} skel loaded successfully", success);

            _logger.LogCurrentProcessMemoryUsage();

            // 从工作区加载需要同步一次时间轴
            lock (_spineObjectModels.Lock)
            {
                foreach (var sp in _spineObjectModels) 
                    sp.ResetAnimationsTime();
            }
        }

        /// <summary>
        /// 安全地在列表头添加一个模型, 发生错误会输出日志
        /// </summary>
        /// <returns>是否添加成功</returns>
        private bool InsertSpineObject(SpineObjectWorkspaceConfigModel cfg)
        {
            try
            {
                var sp = new SpineObjectModel(cfg);
                lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
                if (Application.Current.Dispatcher.CheckAccess())
                {
                    RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                    RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
                }
                else
                {
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
                        RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
                    });
                }
                return true;
            }
            catch (Exception ex)
            {
                _logger.Trace(ex.ToString());
                _logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
            }
            return false;
        }

        #endregion
    }
}
