using Aitex.Core.RT.Log;
using Caliburn.Micro;
using Caliburn.Micro.Core;
using MECF.Framework.Common.DataCenter;
using MECF.Framework.UI.Client.CenterViews.Editors.Sequence;
using MECF.Framework.UI.Client.ClientBase;
using OpenSEMI.ClientBase;
using OpenSEMI.ClientBase.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.IO;
using System.Windows.Forms.VisualStyles;
using MECF.Framework.Common.Account.Permissions;
using MECF.Framework.UI.Client.Core;
using MECF.Framework.UI.Client.RecipeEditorLib.DGExtension.CustomColumn;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel;
using MECF.Framework.UI.Client.RecipeEditorLib.RecipeModel.Params;
using Microsoft.Win32;
using MECF.Framework.UI.Core.Accounts;
using MECF.Framework.Common.OperationCenter;
using DocumentFormat.OpenXml;
using System.Xml;
using MECF.Framework.Common.Account.Extends;
using MECF.Framework.Common.Utilities;
using System.Windows.Data;
using OpenSEMI.ClientBase.IO;
using System.Reflection;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;

namespace MECF.Framework.UI.Client.CenterViews.Editors.Recipe
{
    public class RecipeEditorViewModel : UiViewModelBase, ISupportMultipleSystem, IHandle<UserMode>, IHandle<CommonAggregateEvents> //BaseModel
    {

        #region Variables

        private IRecipeGasFlowCalculator _recipeGasFlowCalculator;
        private RecipeData _currentRecipe;
        private bool _isLoading;
        private int _errorsCount;
        private Window _winValidationInfo;
        private EditMode _editMode;
        private readonly RecipeFormatBuilder _columnBuilder;
        private readonly RecipeProvider _recipeProvider;
        private ObservableCollection<ProcessTypeFileItem> _processTypeFileList;
        private Point? _lastPosOfValidationWin = null;

        private RecipeHistory recipeHistory;

        #endregion

        #region Constructors

        public RecipeEditorViewModel()
        {
            var eventAggregator = IoC.Get<IEventAggregator>();
            eventAggregator?.Subscribe(this);
            _columnBuilder = new RecipeFormatBuilder();
            _recipeProvider = new RecipeProvider();
        }

        #endregion

        #region Commands

        private ICommand _RenameFolderCommand;

        public ICommand RenameFolderCommand
        {
            get
            {
                if (_RenameFolderCommand == null)
                    _RenameFolderCommand = new BaseCommand(() => RenameFolder());
                return _RenameFolderCommand;
            }
        }

        private ICommand _DeleteFolderCommand;

        public ICommand DeleteFolderCommand
        {
            get
            {
                if (_DeleteFolderCommand == null)
                    _DeleteFolderCommand = new BaseCommand(() => DeleteFolder());
                return _DeleteFolderCommand;
            }
        }

        private ICommand _NewFolderCommand;

        public ICommand NewFolderCommand
        {
            get
            {
                if (_NewFolderCommand == null)
                    _NewFolderCommand = new BaseCommand(() => NewFolder());
                return _NewFolderCommand;
            }
        }

        private ICommand _NewFolderRootCommand;

        public ICommand NewFolderRootCommand
        {
            get
            {
                if (_NewFolderRootCommand == null)
                    _NewFolderRootCommand = new BaseCommand(() => NewFolderRoot());
                return _NewFolderRootCommand;
            }
        }

        private ICommand _NewRecipeCommand;

        public ICommand NewRecipeCommand
        {
            get
            {
                if (_NewRecipeCommand == null)
                    _NewRecipeCommand = new BaseCommand(() => NewRecipe());
                return _NewRecipeCommand;
            }
        }

        private ICommand _NewRecipeRootCommand;

        public ICommand NewRecipeRootCommand
        {
            get
            {
                if (_NewRecipeRootCommand == null)
                    _NewRecipeRootCommand = new BaseCommand(() => NewRecipeRoot());
                return _NewRecipeRootCommand;
            }
        }

        private ICommand _RenameRecipeCommand;

        public ICommand RenameRecipeCommand
        {
            get
            {
                if (_RenameRecipeCommand == null)
                    _RenameRecipeCommand = new BaseCommand(() => RenameRecipe());
                return _RenameRecipeCommand;
            }
        }

        private ICommand _DeleteRecipeCommand;

        public ICommand DeleteRecipeCommand
        {
            get
            {
                if (_DeleteRecipeCommand == null)
                    _DeleteRecipeCommand = new BaseCommand(() => DeleteRecipe());
                return _DeleteRecipeCommand;
            }
        }

        private ICommand _SaveAsRecipeCommand;

        public ICommand SaveAsRecipeCommand
        {
            get
            {
                if (_SaveAsRecipeCommand == null)
                    _SaveAsRecipeCommand = new BaseCommand(() => SaveAsRecipe());
                return _SaveAsRecipeCommand;
            }
        }

        #endregion

        #region Properties

        public ObservableCollection<ProcessTypeFileItem> ProcessTypeFileList
        {
            get => _processTypeFileList;
            set
            {
                _processTypeFileList = value;
                NotifyOfPropertyChange(nameof(ProcessTypeFileList));
            }
        }

        public RecipeData CurrentRecipe
        {
            get => _currentRecipe;
            set
            {
                _currentRecipe = value;
                NotifyOfPropertyChange();
            }
        }

        private string _expand = "Fold";

        public string Expand
        {
            get => _expand;
            set
            {
                _expand = value;
                NotifyOfPropertyChange();
            }
        }

        public FileNode CurrentFileNode { get; set; }

        private bool IsChanged => _editMode == EditMode.Edit || CurrentRecipe.IsChanged;

        public Dictionary<string, List<EditorDataGridTemplateColumnBase>> DicColunms { get; set; }

        

        public bool EnableNew { get; set; }

        public bool EnableReName { get; set; }

        public bool EnableCopy { get; set; }

        public bool EnableDelete { get; set; }

        public bool EnableSave { get; set; }

        public bool EnableImportExport { get; set; }

        public bool EnableStep { get; set; }

        public bool EnableReload { get; set; }

        public bool EnableSaveToAll { get; set; }

        public bool EnableSaveTo { get; set; }

        public bool EnableLeftTabPanel { get; set; }

        public bool EnableRefreshRecipeList { get; set; }

        public bool EnableFilterTreeList { get; set; }

        public bool EnableCellPermButton { get; set; }

        /// <summary>
        /// 返回是否显示单元格访问权限编辑按钮。
        /// </summary>
        public bool IsShowCellAccessPermEditButton { get; private set; }

        public int ErrorsCount
        {
            set
            {
                // 如果错误数没发生变化，不要修改Badge的值，以免触发Badge动画。
                if (value == _errorsCount)
                    return;

                if (value == 0)
                    ((RecipeEditorView)View).txtErrorCount.Badge = null;
                else
                    ((RecipeEditorView)View).txtErrorCount.Badge = value;

                _errorsCount = value;
            }
        }

        private int _countAccessibleWhitelist;

        private int AccessibleWhitelistCount
        {
            set
            {
                if (value == _countAccessibleWhitelist)
                    return;

                if (value == 0)
                    ((RecipeEditorView)View).txtCellAccessPremCount.Badge = null;
                else
                    ((RecipeEditorView)View).txtCellAccessPremCount.Badge = value;

                _countAccessibleWhitelist = value;
            }
        }

        /// <summary>
        /// 是否正在加载配方。
        /// </summary>
        public bool IsLoading
        {
            get => _isLoading;
            private set
            {
                _isLoading = value;
                NotifyOfPropertyChange(nameof(IsLoading));
            }
        }

        public List<RecipeStep> SelectedRecipeSteps { get; set; }

       

        public int ChamberTypeIndexSelection { get; set; }

        public int ProcessTypeIndexSelection { get; set; }

        public string CurrentChamberType { get; set; }

        //eg:   Sic//PM1//Process
        public string RecipeBasePath => $"{CurrentChamberType}\\{SystemName}\\{CurrentProcessType}";

        public RecipeType CurrentProcessType => ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType;


        public Visibility MultiChamberVisibility { get; set; }

        public ObservableCollection<string> Chambers { get; set; }

        public string SelectedChamber { get; set; }

        public object View { get; set; }


        #endregion

        #region VM Methods

        protected override void OnInitialize()
        {
            base.OnInitialize();
            UpScConfig();
            CurrentChamberType = (string)QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedChamberType");

           

            var scProcessType = QueryDataClient.Instance.Service.GetConfig("System.Recipe.SupportedProcessType").ToString();
            var ProcessType = new ObservableCollection<string>(((string)(scProcessType)).Split(','));

            ProcessTypeFileList = new ObservableCollection<ProcessTypeFileItem>();
            

            for (var i = 0; i < ProcessType.Count; i++)
            {
                var type = new ProcessTypeFileItem();
                 Enum.TryParse(ProcessType[i], true, out RecipeType rType) ;
                 type.ProcessType = rType;
               //eg:   Sic//PM1//Process
               var prefix = $"{CurrentChamberType}\\{SystemName}\\{ProcessType[i]}";
                var recipes = _recipeProvider.GetXmlRecipeList(prefix);
                type.FileListByProcessType =
                    RecipeSequenceTreeBuilder.BuildFileNode(prefix, "", false, recipes)[0].Files;
                ProcessTypeFileList.Add(type);
               
            }

            DicColunms = new Dictionary<string, List<EditorDataGridTemplateColumnBase>>();
        }

        private void UpScConfig()
        {
            _recipeGasFlowCalculator = RecipeGasFlowCalculatorFactory.GetGasFlowCalculator(SystemName);
        }

        protected override void OnActivate()
        {
            LOG.Debug("Building recipe format.");

            UpScConfig();
            UpdateRecipeFormat();

            Task tRecipeLoading = null;
            if (CurrentFileNode is { IsFile: true })
            {
                LOG.Debug($"Loading recipe, {CurrentFileNode.PrefixPath}/{CurrentFileNode.FullPath}");
                tRecipeLoading = LoadRecipe(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath).ContinueWith(t =>
                {
                    Execute.OnUIThread(() =>
                    {
                        // 根据角色权限设置判断是否显示按钮。
                        LOG.Debug("Get permission of CAPE button");
                        IsShowCellAccessPermEditButton = GetCapeModeButtonPerm();
                        NotifyOfPropertyChange(nameof(IsShowCellAccessPermEditButton));

                        LOG.Debug("Update view");
                        UpdateView();

                        LOG.Debug("Run base.OnActive");
                        base.OnActivate();
                    });
                });
            }

            if (tRecipeLoading == null)
            {
                // 根据角色权限设置判断是否显示按钮。
                LOG.Debug("Get permission of CAPE button");
                IsShowCellAccessPermEditButton = GetCapeModeButtonPerm();
                NotifyOfPropertyChange(nameof(IsShowCellAccessPermEditButton));

                LOG.Debug("Update view");
                UpdateView();

                LOG.Debug("Run base.OnActive");
                base.OnActivate();
            }

        }

        protected override void OnDeactivate(bool close)
        {
            base.OnDeactivate(close);

            if (IsChanged)
            {
                if (DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
                        $"Recipe {CurrentRecipe.Name} content is changed, do you want to save it?") == DialogButton.Yes)
                {
                    SaveRecipe();
                }
            }

            _winValidationInfo?.Close();

            // 锁定编辑器
            ((RecipeEditorView)View).editorLocker.Lock();
        }

        protected override void OnViewLoaded(object view)
        {
            View = view;
            base.OnViewLoaded(view);
        }

        #endregion

        /// <summary>
        /// 获取角色权限配置中“Recipe.Behaviour.AllowEditCellAccessPerm”的配置。
        /// </summary>
        /// <returns></returns>
        private bool GetCapeModeButtonPerm()
        {
            var roleID = BaseApp.Instance.UserContext.Role.RoleId;
            var val = AccountClient.Instance.Service.GetMenuPermission(roleID, "Recipe.Behaviour.AllowEditCellAccessPerm");
            return val != (int)MenuPermissionEnum.MP_NONE;
        }

        public void UpdateRecipeFormat()
        {
            var chamber = QueryDataClient.Instance.Service.GetConfig("System.Recipe.ChamberModules")?.ToString() ?? "PM1";

            Chambers = new ObservableCollection<string>(chamber.Split(','));
            if (Chambers.Count > 1)
            {
                for (var i = 0; i < Chambers.Count; i++)
                {
                    var isPmInstall =
                        (bool)QueryDataClient.Instance.Service.GetConfig(
                            $"System.SetUp.Is{Chambers[i].ToString()}Installed");
                    {
                        if (!isPmInstall)
                        {
                            Chambers.RemoveAt(i);
                        }
                    }
                }
            }

            // 确保至少显示PM1
            if (Chambers.Count == 0)
                Chambers = new ObservableCollection<string>(new[] { "PM1" });

            MultiChamberVisibility = Chambers.Count > 1 ? Visibility.Visible : Visibility.Collapsed;
            SelectedChamber = Chambers[0];

            var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId);
            //// 如果RecipeData为空，则先创建其实例
            //if (CurrentRecipe == null)
            //{
            var recipeData =new RecipeData(_recipeGasFlowCalculator, CurrentProcessType);
            recipeData.BuildFormat($"{CurrentChamberType}\\{CurrentProcessType}", SystemName, role);
            recipeData.OnValidated += (sender, args) =>
            {
                ErrorsCount = recipeData.ValidationErrorCount;
            };

            recipeData.OnAccessibleWhitelistChanged += (sender, count) =>
            {
                AccessibleWhitelistCount = count;
            };
            CurrentRecipe = recipeData;
            _editMode = EditMode.None;

            //}
            //else
            //{
            //    CurrentRecipe.UpdatePermission(role);
            //    CurrentRecipe.SetRecipeGasFlowCalculator(_recipeGasFlowCalculator);
            //}
        }

        /// <summary>
        /// Process/Routine/Clean Tab选择变化事件。
        /// </summary>
        public void TabSelectionChanged()
        {
            UpdateRecipeFormat();
            OnViewLoaded(View);
        }

        /// <summary>
        /// Chamber变更时重新加载Recipe。
        /// </summary>
        public async void ChamberSelectionChanged()
        {
            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
                    DialogType.CONFIRM,
                    $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            IsLoading = true;

            var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId);
            await CurrentRecipe.ChangeChamber(CurrentRecipe.Columns
                , _columnBuilder.Configs, SelectedChamber, role, GetLoadingDispatcher());

            IsLoading = false;
        }

        /// <summary>
        /// 左侧Recipe列表选择改变时，重新加载Recipe。
        /// </summary>
        /// <param name="node"></param>
        public async void TreeSelectChanged(FileNode node)
        {

            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
                    DialogType.CONFIRM,
                    $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            //ChangeRecipeData();

            CurrentFileNode = node;

            if (node is { IsFile: true })
            {
                await LoadRecipe(node.PrefixPath, node.FullPath);
            }
            else
            {
                ClearData();
                _editMode = EditMode.None;
            }

            UpdateView();
        }


        #region folder

        public void NewFolder()
        {
            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
            dialog.FileName = "new folder";
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var name = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(name))
            {
                DialogBox.ShowWarning("Folder name should not be empty");
                return;
            }

            var prefix = RecipeBasePath;// $"{ChamberType[ChamberTypeIndexSelection]}\\{ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType}\\{SystemName}";
            var processType = string.Empty;
            var newFolder = string.Empty;
            if (CurrentFileNode != null)
            {
                prefix = CurrentFileNode.PrefixPath;
                var folder = CurrentFileNode.FullPath;
                if (CurrentFileNode.IsFile)
                {
                    folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
                    if (!string.IsNullOrEmpty(folder))
                        newFolder = folder;
                }
                else
                {
                    newFolder = folder + "\\";
                }

            }

            newFolder = newFolder + name;

            if (IsExist(newFolder, false))
            {
                DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder with the same name already exist.");
                return;
            }

            if (newFolder.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200.");
                return;
            }

            _recipeProvider.CreateRecipeFolder(prefix, newFolder);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true);
        }

        public void NewFolderRoot()
        {
            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
            dialog.FileName = "new folder";
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var name = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(name))
            {
                DialogBox.ShowWarning("Folder name should not be empty");
                return;
            }


            if (IsExist(name, false))
            {
                DialogBox.ShowWarning($"Can not create folder {name}, Folder with the same name already exist.");
                return;
            }

            if (name.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {name}, Folder name too long, should be less 200.");
                return;
            }

            var prefix = RecipeBasePath;// $"{ChamberType[ChamberTypeIndexSelection]}\\{ProcessTypeFileList[ProcessTypeIndexSelection].ProcessType}\\{SystemName}";

            _recipeProvider.CreateRecipeFolder(prefix, name);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, name, true);
        }

        public void DeleteFolder()
        {
            if (CurrentFileNode == null || CurrentFileNode.IsFile)
                return;

            if (CurrentFileNode.Files.Count > 0)
            {
                DialogBox.ShowWarning(
                    $"Can not delete non-empty folder, Remove the files or folders under \r\n{CurrentFileNode.FullPath}.");
                return;
            }

            var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
                $"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?");
            if (selection == DialogButton.No)
                return;

            var nextFocus = CurrentFileNode.Parent.FullPath;
            var isFolder = true;
            if (CurrentFileNode.Parent.Files.Count > 1)
            {
                for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++)
                {
                    if (CurrentFileNode.Parent.Files[i] == CurrentFileNode)
                    {
                        if (i == 0)
                        {
                            nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath;
                            isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile;
                        }
                        else
                        {
                            nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath;
                            isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile;
                        }
                    }
                }
            }

            _recipeProvider.DeleteRecipeFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder);
        }

        public void RenameFolder()
        {
            if (CurrentFileNode == null || CurrentFileNode.IsFile)
                return;

            var dialog = new InputFileNameDialogViewModel("Input New Folder Name");
            dialog.FileName = CurrentFileNode.Name;
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var name = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(name))
                return;

            var newFolder = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1);
            if (!string.IsNullOrEmpty(newFolder))
                newFolder = newFolder + name;
            else
                newFolder = name;

            if (newFolder == CurrentFileNode.FullPath)
                return;

            if (IsExist(newFolder, false))
            {
                DialogBox.ShowWarning($"Can not rename to {newFolder}, Folder with the same name already exist.");
                return;
            }

            if (newFolder.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {newFolder}, Folder name too long, should be less 200.");
                return;
            }

            _recipeProvider.RenameFolder(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath, newFolder);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newFolder, true);
        }

        #endregion

        #region recipe

        public void NewRecipe()
        {
            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Name");
            dialog.FileName = "";
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var recipeName = dialog.FileName.Trim();
            string gemRecipeName = recipeName + ".rcp";
            if (string.IsNullOrEmpty(dialog.FileName))
            {
                DialogBox.ShowWarning("Recipe file name should not be empty");
                return;
            }
            //eg:   Sic//PM1//Process

            var processType = string.Empty;
            if (CurrentFileNode != null)
            {
                var folder = CurrentFileNode.FullPath;


                if (CurrentFileNode.IsFile)
                {
                    folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
                    //if (!string.IsNullOrEmpty(folder))
                    //    folder = folder;
                }
                else
                {
                    folder = folder + "\\";
                }

                recipeName = folder + recipeName;
            }

            if (IsExist(recipeName, true))
            {
                DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist.");
                return;
            }


            if (recipeName.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
                return;
            }

            var recipe = new RecipeData(_recipeGasFlowCalculator, CurrentProcessType);
            recipe.Name = recipeName;
            recipe.PrefixPath = RecipeBasePath;
            recipe.Creator = BaseApp.Instance.UserContext.LoginName;
            recipe.CreateTime = DateTime.Now;
            recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
            recipe.ReviseTime = DateTime.Now;
            recipe.Description = string.Empty;

            if (!Save(recipe, true))
                return;

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false);

            InvokeClient.Instance.Service.DoOperation($"GEM_PPChange", @"Sic\Process\" + gemRecipeName, 1);
        }

        public void NewRecipeRoot()
        {
            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
            dialog.FileName = "new recipe";
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var recipeName = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(dialog.FileName))
            {
                DialogBox.ShowWarning("Recipe file name should not be empty");
                return;
            }

            if (IsExist(recipeName, true))
            {
                DialogBox.ShowWarning($"Can not create {recipeName}, Recipe with the same name already exist.");
                return;
            }


            if (recipeName.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
                return;
            }

            var recipe = new RecipeData(_recipeGasFlowCalculator, CurrentProcessType);
            recipe.Name = recipeName;
            recipe.PrefixPath = RecipeBasePath;
            recipe.Creator = BaseApp.Instance.UserContext.LoginName;
            recipe.CreateTime = DateTime.Now;
            recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
            recipe.ReviseTime = DateTime.Now;
            recipe.Description = string.Empty;

            if (!Save(recipe, true))
                return;


            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false);
        }

        public void IsExpandAllNodes()
        {
            var recipeList = ProcessTypeFileList.FirstOrDefault(x => x.ProcessType == CurrentProcessType);
            if (recipeList == null)
                return;

            bool isExpanded;
            if (Expand == "Expand")
            {
                isExpanded = true;
                Expand = "Fold";
            }
            else
            {
                isExpanded = false;
                Expand = "Expand";
            }
            foreach (var item in recipeList.FileListByProcessType)
            {
                CollapseNode(item, isExpanded);
            }

            recipeList.InvokePropertyChanged();
        }

        private void CollapseNode(FileNode treeViewItem,bool isExpanded)
        {
            if (treeViewItem == null) return;

            // 折叠当前节点
            treeViewItem.IsExpanded = isExpanded;
            // 如果有子节点，递归折叠
            foreach (var childItem in treeViewItem.Files)
            {
                
                CollapseNode(childItem, isExpanded); // 递归折叠子节点
            }
        }


        private void ReloadRecipeFileList(string chamberType, RecipeType processType, string selectedFile,
            bool selectionIsFolder)
        {
            var item = ProcessTypeFileList.FirstOrDefault(x => x.ProcessType == processType);
            if (item == null)
            {
                LOG.Write("error reload recipe file list, type = " + processType);
            }

            var prefix = RecipeBasePath;// $"{ChamberType[ChamberTypeIndexSelection]}\\{item.ProcessType}\\{SystemName}";
            var recipes = _recipeProvider.GetXmlRecipeList(prefix);

            item.FileListByProcessType =
                RecipeSequenceTreeBuilder.BuildFileNode(prefix, selectedFile, selectionIsFolder, recipes)[0].Files;

            item.InvokePropertyChanged();
        }

        private bool IsExist(string fullPath, bool isFile)
        {
            for (var i = 0; i < ProcessTypeFileList.Count; i++)
            {
                if (ProcessTypeFileList[i].ProcessType == CurrentProcessType)
                {
                    if (ProcessTypeFileList[i].FileListByProcessType.Count == 0)
                        return false;

                    return FindFile(fullPath, ProcessTypeFileList[i].FileListByProcessType[0].Parent, isFile);
                }
            }

            return true;
        }

        private bool FindFile(string path, FileNode root, bool isFile)
        {
            if (root.FullPath == path && !isFile)
            {
                return true;
            }

            foreach (var node in root.Files)
            {
                if (isFile && node.IsFile && node.FullPath == path)
                    return true;

                if (!node.IsFile && FindFile(path, node, isFile))
                    return true;
            }

            return false;
        }

        public void SaveAsRecipe()
        {
            if (CurrentFileNode == null || !CurrentFileNode.IsFile)
                return;

            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;
                if (selection == DialogButton.No)
                    CurrentRecipe.Steps.Save(); // 重置为已保存状态,不然会多次提示未保存
                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
            dialog.FileName = CurrentFileNode.Name;
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var recipeName = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(dialog.FileName))
            {
                DialogBox.ShowWarning("Recipe file name should not be empty");
                return;
            }


            var processType = string.Empty;

            var folder = CurrentFileNode.FullPath;
            if (CurrentFileNode.IsFile)
            {
                folder = folder.Substring(0, folder.LastIndexOf("\\") + 1);
            }

            if (!string.IsNullOrEmpty(folder))
                recipeName = folder + "\\" + recipeName;

            if (CurrentFileNode.FullPath == recipeName)
                return;

            if (IsExist(recipeName, true))
            {
                DialogBox.ShowWarning($"Can not copy to {recipeName}, Recipe with the same name already exist.");
                return;
            }


            if (recipeName.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {recipeName}, Folder name too long, should be less 200.");
                return;
            }
            CopyRecipePm1ToPm2(CurrentRecipe);
            if (!CurrentRecipe.GetXmlString(out var xml, out var reason))
            {
                DialogBox.ShowError($"Unable to generate recipe content, {reason}");
                return;
            }

            CurrentRecipe.Creator = BaseApp.Instance.UserContext.LoginName;
            CurrentRecipe.CreateTime = DateTime.Now;
            CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
            CurrentRecipe.ReviseTime = DateTime.Now;
            CurrentRecipe.Description = CurrentRecipe.Description + ". Renamed from " + CurrentFileNode.Name;

            _recipeProvider.SaveAsRecipe(RecipeBasePath, recipeName, xml);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, recipeName, false);
        }

        public void RenameRecipe()
        {
            if (CurrentFileNode == null || !CurrentFileNode.IsFile)
                return;

            if (IsChanged)
            {
                var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No | DialogButton.Cancel,
                    DialogType.CONFIRM, $"Recipe {CurrentRecipe.Name} is changed, do you want to save it?");
                if (selection == DialogButton.Cancel)
                    return;

                if (selection == DialogButton.Yes)
                {
                    CurrentRecipe.Revisor = BaseApp.Instance.UserContext.LoginName;
                    CurrentRecipe.ReviseTime = DateTime.Now;
                    Save(CurrentRecipe, false);
                }
            }

            var dialog = new InputFileNameDialogViewModel("Input New Recipe Name");
            dialog.FileName = CurrentFileNode.Name;
            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var recipeName = dialog.FileName.Trim();
            if (string.IsNullOrEmpty(dialog.FileName))
            {
                DialogBox.ShowWarning("Recipe file name should not be empty");
                return;
            }


            var processType = string.Empty;

            var newName = CurrentFileNode.FullPath.Substring(0, CurrentFileNode.FullPath.LastIndexOf("\\") + 1);
            if (!string.IsNullOrEmpty(newName))
                newName = newName + recipeName;
            else
                newName = recipeName;

            if (newName == CurrentFileNode.FullPath)
                return;

            if (IsExist(newName, true))
            {
                DialogBox.ShowWarning($"Can not rename to {newName}, Recipe with the same name already exist.");
                return;
            }


            if (newName.Length > 200)
            {
                DialogBox.ShowWarning($"Can not create folder {newName}, Folder name too long, should be less 200.");
                return;
            }

            _recipeProvider.RenameRecipe(RecipeBasePath, CurrentFileNode.FullPath, newName);

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, newName, false);
        }

        public void DeleteRecipe()
        {
            if (CurrentFileNode == null || !CurrentFileNode.IsFile)
                return;

            var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No, DialogType.CONFIRM,
                $"Are you sure you want to delete \r\n {CurrentFileNode.FullPath}?");
            if (selection == DialogButton.No)
                return;

            string gemRecipeName = CurrentFileNode.FullPath;

            var nextFocus = CurrentFileNode.Parent.FullPath;
            var isFolder = true;
            if (CurrentFileNode.Parent.Files.Count > 1)
            {
                for (var i = 0; i < CurrentFileNode.Parent.Files.Count; i++)
                {
                    if (CurrentFileNode.Parent.Files[i] == CurrentFileNode)
                    {
                        if (i == 0)
                        {
                            nextFocus = CurrentFileNode.Parent.Files[i + 1].FullPath;
                            isFolder = !CurrentFileNode.Parent.Files[i + 1].IsFile;
                        }
                        else
                        {
                            nextFocus = CurrentFileNode.Parent.Files[i - 1].FullPath;
                            isFolder = !CurrentFileNode.Parent.Files[i - 1].IsFile;
                        }
                    }
                }
            }

            _recipeProvider.DeleteRecipe(CurrentFileNode.PrefixPath, CurrentFileNode.FullPath);
            CurrentRecipe.DeleteAccessibleWhiteList();

            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, nextFocus, isFolder);

            InvokeClient.Instance.Service.DoOperation($"GEM_PPChange", @"Sic\Process\" + gemRecipeName + ".rcp", 3);
        }

        public void RefreshRecipe()
        {
            ReloadRecipeFileList(CurrentChamberType, CurrentProcessType, "", false);
        }

        public async void ReloadRecipe()
        {
            if (_editMode == EditMode.Normal || _editMode == EditMode.Edit)
            {
                if (CurrentRecipe == null)
                    return;

                if (CurrentRecipe.IsChanged)
                {
                    var ret = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
                        DialogType.CONFIRM,
                        $"Recipe {CurrentRecipe.Name} has changed, discard and reload?");
                    if (ret == DialogButton.No)
                        return;
                }

                await LoadRecipe(CurrentRecipe.PrefixPath, CurrentRecipe.Name);
                CurrentRecipe.Validate();
                UpdateView();

                foreach (var step in CurrentRecipe.Steps)
                    step.Save();
            }
        }

        public void SaveToAll()
        {
            CurrentRecipe.Validate();

            if (!CurrentRecipe.IsCompatibleWithCurrentFormat)
            {
                DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file");
                return;
            }
            bool isDoRecipeHistorySaveDB = CurrentRecipe.IsChanged;//Recipe保存前，变量缓存状态    使用内容CurrentRecipe.LastRevisionTime保存后值会发生改变，



            //var selection = DialogBox.ShowDialog(DialogButton.Yes | DialogButton.No,
            //    DialogType.CONFIRM,
            //    $"Do you want to save to all chambers? \r\n This will replace all the other chambers recipe content");
            //if (selection == DialogButton.No)
            //    return;

            if (!CurrentRecipe.SaveTo(Chambers.ToArray(), out var reason))
            {
                DialogBox.ShowError($"Unable to save recipe, {reason}");
                return;
            }

            Save(CurrentRecipe, false);

            if (isDoRecipeHistorySaveDB)
                RecipeMemorySaveDB();

            InvokeClient.Instance.Service.DoOperation($"GEM_PPChange", @"Sic\Process\" + CurrentRecipe.Name + ".rcp", 2);
        }
        /// <summary>
        /// 先同步CurrentRecipe在内存(界面所看到的，不一定是XML)中PM1和PM2中的数据，
        /// </summary>
        /// <param name="recipe"></param>
        private void CopyRecipePm1ToPm2(RecipeData recipe)
        {
            if (!recipe.SaveTo(Chambers.ToArray(), out var reason))
            {
                DialogBox.ShowError($"Unable to save recipe, {reason}");
                return;
            }
        }

        public void SaveTo()
        {
            CurrentRecipe.Validate();

            if (!CurrentRecipe.IsCompatibleWithCurrentFormat)
            {
                DialogBox.ShowWarning($"Saving failed, {CurrentRecipe.Name} is not a valid recipe file.");
                return;
            }

            var dialog =
                new SaveToDialogViewModel("Select the chamber to copy to", SelectedChamber, Chambers.ToList());

            var wm = new WindowManager();
            var dialogReturn = wm.ShowDialog(dialog);
            if (!dialogReturn.HasValue || !dialogReturn.Value)
                return;

            var chambers = new List<string>();
            foreach (var dialogChamber in dialog.Chambers)
            {
                if (dialogChamber.IsEnabled && dialogChamber.IsChecked)
                    chambers.Add(dialogChamber.Name);
            }

            if (chambers.Count == 0)
                return;

            if (!CurrentRecipe.SaveTo(Chambers.ToArray(), out var reason))
            {
                DialogBox.ShowError($"Unable to save recipe, {reason}");
                return;
            }

            Save(CurrentRecipe, false);
        }

        /// <summary>
        /// 导入Recipe。
        /// </summary>
        public void ImportRecipe()
        {
            try
            {
                IsLoading = true;

                // 选择文件
                var openFileDialog = new OpenFileDialog()
                {
                    Filter = "Sic Recipe File|*.srp",
                    Title = "Import Recipe",
                    DefaultExt = ".srp",
                    Multiselect = true
                };

                var prefix = ProcessTypeFileItem.GetProcessFilesPrefix(RecipeType.Process);

                if (openFileDialog.ShowDialog() != true)
                    return;

                #region 选择要导入的文件夹

                var dialog = new RecipeSelectDialogViewModel(true, true, RecipeType.Process)
                {
                    DisplayName = "Select Folder to Import ..."
                };

                var wm = new WindowManager();
                var bret = wm.ShowDialog(dialog);

                #endregion

                if (bret != true)
                    return;

                var recipeFolderToImport
                    = dialog.DialogResult;

                // 从文件夹名移除Prefix
                recipeFolderToImport = recipeFolderToImport.Replace(prefix + "\\", "");

                var fns = openFileDialog.FileNames;

                // 是否覆盖所有Recipe，该选项通过Overwrite对话框给值。
                var isOverwriteAll = false;

                foreach (var fn in fns)
                {
                    try
                    {
                        var recipePath = $"{recipeFolderToImport}\\{Path.GetFileNameWithoutExtension(fn)}";

                        // 检查Recipe是否已存在
                        if (IsExist(recipePath.TrimStart('\\'), true) && !isOverwriteAll)
                        {

                            var ret = DialogBox.ShowDialog(
                                DialogButton.Yes | DialogButton.YesToAll | DialogButton.Cancel,
                                DialogType.WARNING,
                                $"{recipePath} has existed, overwrite?\r\n\r\nATTENTION: Press 'Yes To All' to overwrite all files.");
                            if (ret == DialogButton.Cancel)
                                return;

                            if (ret == DialogButton.YesToAll)
                                isOverwriteAll = true;
                        }

                        RecipeData.ImportRecipe(fn, out var xmlContent);

                        // 保存文件。
                        _recipeProvider.WriteRecipeFile(prefix, recipePath, xmlContent);
                    }
                    catch (Exception ex)
                    {
                        DialogBox.ShowError($"Unable to import {fn}, {ex}");
                    }
                }

                // 刷新Recipe列表
                ReloadRecipeFileList("Sic", RecipeType.Process, "", false);


            }
            finally
            {
                IsLoading = false;
            }

        }

        /// <summary>
        /// 到处Recipe。
        /// </summary>
        public void ExportRecipe()
        {
            if (CurrentRecipe == null)
            {
                MessageBox.Show("No recipe selected.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }


            if (string.IsNullOrEmpty(CurrentRecipe.Name))
            {
                MessageBox.Show("No recipe loaded.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // 选择保存路径和文件名
            var dialog = new SaveFileDialog
            {
                Filter = "Sic Recipe File|*.srp",
                Title = "Export Recipe",
                DefaultExt = ".srp",
                FileName = CurrentRecipe.Name.Split('\\').Last()
            };
            var ret = dialog.ShowDialog();
            if (!ret.HasValue || ret == false)
                return;

            // 保存文件
            try
            {
                if (CurrentRecipe.ExportRecipe(dialog.FileName, out var reason))
                    DialogBox.ShowInfo($"Export done! {dialog.FileName}");
                else
                    DialogBox.ShowError($"Unable to export recipe, {reason}");
            }
            catch (Exception ex)
            {
                DialogBox.ShowError($"Unable to export recipe, {ex}");
            }


        }

        #endregion

        #region Steps

        public void SaveRecipe()
        {
            /*if (Save(CurrentRecipe, false))
                MessageBox.Show($"The recipe has been saved successfully!", "Succeeded", MessageBoxButton.OK,
                    MessageBoxImage.Information);*/

            Save(CurrentRecipe, false);
        }

        public void PopSetting(string controlName, Param paramData)
        {
            var stepNum = Convert.ToInt32(((StepParam)paramData.Parent[1]).Value);

            var dialog = new PublicPopSettingDialogViewModel();
            dialog.DisplayName = paramData.DisplayName;

            var step = CurrentRecipe.PopSettingSteps[controlName][stepNum - 1];

            var controlParameters = new RecipeStep(null);
            var brandParameters = new ObservableCollection<BandParam>();
            foreach (var item in step)
            {
                if (item.ControlName.Contains("Band"))
                {
                    var name = item.ControlName.Replace("Wavelength", "").Replace("Bandwidth", "");
                    var displayName = item.DisplayName.Replace("Wavelength", "").Replace("Bandwidth", "");

                    if (brandParameters.All(x => x.ControlName != name))
                    {
                        brandParameters.Add(new BandParam()
                        {
                            ControlName = name,
                            DisplayName = displayName
                        });
                    }

                    if (item.ControlName.Contains("Wavelength"))
                    {
                        brandParameters.First(x => x.ControlName == name).WavelengthDoubleParam = item;
                    }
                    else if (item.ControlName.Contains("Bandwidth"))
                    {
                        brandParameters.First(x => x.ControlName == name).BandwidthDoubleParam = item;
                    }
                }
                else
                    controlParameters.Add(item);
            }

            dialog.Parameters = step;
            dialog.ControlParameters = controlParameters;
            dialog.BandParameters = brandParameters;

            var wm = new WindowManager();
            var bret = wm.ShowDialog(dialog);
            if (bret == true)
            {
                CurrentRecipe.PopSettingSteps[controlName][stepNum - 1] = dialog.Parameters;
            }
        }

        private bool Save(RecipeData recipe, bool createNew)
        {
            if (string.IsNullOrEmpty(recipe.Name))
            {
                MessageBox.Show("Recipe name can't be empty", "Error",
                    MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }

            if (recipe.Validate() == false)
            {
                var mbr = DialogBox.ShowDialog(
                    DialogButton.Yes | DialogButton.No,
                    DialogType.WARNING,
                    $"Are you sure to continue to save with errors?\r\n\r\n" +
                    $"{CurrentRecipe.ValidationErrorSummary}");
                if (mbr == DialogButton.No)
                    return false;
            }
            CopyRecipePm1ToPm2(recipe);
            recipe.Revisor = BaseApp.Instance.UserContext.LoginName;
            recipe.ReviseTime = DateTime.Now;

            if (!recipe.GetXmlString(out var recipeXmlString, out var reason))
            {
                DialogBox.ShowError($"Unable to generate recipe content, {reason}");
                return false;
            }

            if (string.IsNullOrEmpty(recipeXmlString))
            {
                DialogBox.ShowError($"Unable to generate recipe xml file, the xml string is empty.");
                return false;
            }

            var result = _recipeProvider.WriteRecipeFile(recipe.PrefixPath, recipe.Name, recipeXmlString);

            if (result)
            {
                recipe.MarkAsSaved();
                _editMode = EditMode.Normal;
                UpdateView();
            }
            else
            {
                DialogBox.ShowError("Unable to transfer the recipe to RT, please see the RT logs for details.");
            }

            return result;
        }

        public void AddStep()
        {
            CurrentRecipe.AddStep();

            if (_editMode != EditMode.New && _editMode != EditMode.ReName)
                _editMode = EditMode.Edit;

            UpdateView();
        }

        public void InsertStepToLeft()
        {
            if (CurrentRecipe == null)
                return;

            if (CurrentRecipe.Steps.SelectedSteps.Count != 1)
                return;

            var step = CurrentRecipe.Steps.SelectedSteps[0];
            CurrentRecipe.InsertToLeft(step);

            if (_editMode != EditMode.New && _editMode != EditMode.ReName)
                _editMode = EditMode.Edit;

            UpdateView();
        }

        public void InsertStepToRight()
        {
            if (CurrentRecipe == null)
                return;

            if (CurrentRecipe.Steps.SelectedSteps.Count != 1)
                return;

            var step = CurrentRecipe.Steps.SelectedSteps[0];
            CurrentRecipe.InsertToRight(step);

            if (_editMode != EditMode.New && _editMode != EditMode.ReName)
                _editMode = EditMode.Edit;

            UpdateView();
        }

        /// <summary>
        /// 拷贝所有选中的Steps。
        /// </summary>
        public void CopyStep()
        {
            CurrentRecipe?.CopySteps();
        }

        /// <summary>
        /// 在左侧粘贴Step。
        /// </summary>
        public async void PasteStepToLeft()
        {
            await Paste(true);
        }

        /// <summary>
        /// 在右侧粘贴Step。
        /// </summary>
        public async void PasteStepToRight()
        {
            await Paste(false);
        }

        /// <summary>
        /// 粘贴Step。
        /// <para>注意：async Task 标记的方法不能直接应用于Caliburn.Message.Attach 方法。</para>
        /// </summary>
        /// <param name="isToLeft">是否粘贴到当前选中的Step的左侧。</param>
        /// <returns></returns>
        private async Task Paste(bool isToLeft)
        {
            if (CurrentRecipe == null)
                return;

            await CurrentRecipe.Paste(isToLeft);

            if (_editMode != EditMode.New && _editMode != EditMode.ReName)
                _editMode = EditMode.Edit;

            UpdateView();
        }

        /// <summary>
        /// 在左侧复制Step。
        /// </summary>
        public async void DuplicateStepToLeft()
        {
            await Duplicate(true);
        }

        /// <summary>
        /// 在右侧复制Step。
        /// </summary>
        public async void DuplicateStepToRight()
        {
            await Duplicate(false);
        }

        /// <summary>
        /// 复制选中的Step。
        /// </summary>
        /// <param name="isToLeft">是否复制到选中的Step的左侧。</param>
        private async Task Duplicate(bool isToLeft)
        {
            if (SelectedRecipeSteps == null || SelectedRecipeSteps.Count != 1)
                return;

            CopyStep();
            await Paste(isToLeft);
        }

        /// <summary>
        /// 删除步骤。
        /// </summary>
        public void DeleteStep()
        {
            if (CurrentRecipe == null)
                return;

            CurrentRecipe.DeleteSteps();

            if (_editMode != EditMode.New && _editMode != EditMode.ReName)
                _editMode = EditMode.Edit;

            UpdateView();
        }

        private void CreateValidationDetailWindow()
        {
            _winValidationInfo = new RecipeEditorValidationDetailWindow(
                CurrentRecipe.ValidationErrorInfo,
                _lastPosOfValidationWin);

            _winValidationInfo.DataContext = this;
            _winValidationInfo.Closed += (sender, args) =>
            {
                _lastPosOfValidationWin = new Point(_winValidationInfo.Left, _winValidationInfo.Top);
                _currentRecipe.Steps.ResetHighlight();
            };
        }

        public void ShowValidationDetailWindow()
        {
            CurrentRecipe.Validate();

            if (Application.Current.Windows.OfType<RecipeEditorValidationDetailWindow>().Any())
                _winValidationInfo.Activate();
            else
            {
                CreateValidationDetailWindow();
                _winValidationInfo.Show();
            }
        }

        private TreeViewItem GetParentObjectEx<TreeViewItem>(DependencyObject obj) where TreeViewItem : FrameworkElement
        {
            var parent = VisualTreeHelper.GetParent(obj);
            while (parent != null)
            {
                if (parent is TreeViewItem)
                {
                    return (TreeViewItem)parent;
                }

                parent = VisualTreeHelper.GetParent(parent);
            }

            return null;
        }

        public void TreeRightMouseDown(MouseButtonEventArgs e)
        {
            var item = GetParentObjectEx<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
            if (item != null)
            {
                item.Focus();
            }
        }

        /// <summary>
        /// 滚动到并高亮指定的参数单元格。
        /// </summary>
        /// <param name="param"></param>
        public void FocusToParam(IParam param)
        {
            //((RecipeEditorView)View).dgRecipeEditor.FocusToParam(param);

            ((RecipeEditorView)View).dgCustom.FocusToParam(param);
        }

        #endregion

        #region Cell-Access-Perm-Edit Mode

        public async Task SwitchCellAccessPermEditMode()
        {
            var mode = CurrentRecipe.IsAccessibleWhitelistEditMode;
            if (mode)
            {
                await CurrentRecipe.LeaveAccessibleWhitelistEditMode();
                _editMode = EditMode.Normal;
            }
            else
            {
                var ret = await CurrentRecipe.EnterAccessibleWhitelistEditMode();
                if (!ret.isSucceeded)
                {
                    DialogBox.ShowError($"Can not edit whitelist, {ret.reason}");
                    return;
                }

                _editMode = EditMode.EditWhitelist;
            }

            UpdateView();
        }

        #endregion


        private void ClearData()
        {
            _editMode = EditMode.None;
            CurrentRecipe.Clear();
            CurrentRecipe.Name = string.Empty;
            CurrentRecipe.Description = string.Empty;
        }

        /// <summary>
        /// 根据配置获取Cascade方式呈现Recipe的Dispatcher，或返回null。
        /// </summary>
        /// <returns></returns>
        private static Dispatcher GetLoadingDispatcher()
        {
            // 判断Step呈现方式
            var isCascadeLoading = (bool)QueryDataClient.Instance.Service.GetConfig("System.RecipeCascadeLoading");
            var dispatcher = isCascadeLoading ? Dispatcher.CurrentDispatcher : null;
            return dispatcher;
        }


        private async Task LoadRecipe(string prefixPath, string recipeName)
        {
            IsLoading = true;

            CurrentRecipe.Clear();
            CurrentRecipe.Steps.Save(); // 重置为已保存状态
            ErrorsCount = 0;

            var recipeContent = _recipeProvider.ReadRecipeFile(prefixPath, recipeName);

            if (string.IsNullOrEmpty(recipeContent))
            {
                MessageBox.Show($"{prefixPath}\\{recipeName} is empty, please confirm the file is valid.",
                    "Error",
                    MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            var role = AccountClient.Instance.Service.GetRoleByID(BaseApp.Instance.UserContext.Role.RoleId);
            await CurrentRecipe.LoadFile(prefixPath, recipeName, recipeContent, SelectedChamber,
                role, GetLoadingDispatcher());


            _editMode = EditMode.Normal;
            IsLoading = false;


            InitializeRecipeMemory(CurrentRecipe);

        }

        private void InitializeRecipeMemory(RecipeData recipeData)
        {
            recipeHistory = new RecipeHistory();
            recipeHistory.RoleName = recipeData.Revisor;
            recipeHistory.RecipeName = recipeData.Name.Replace("\\", "-");
            recipeHistory.ProcessType = recipeData.RecipeType.ToString();
            recipeHistory.RecipeFromXML = _recipeProvider.ReadRecipeFile(recipeData.PrefixPath, recipeData.Name);
        }

        private void RecipeMemorySaveDB()
        {
            if (recipeHistory == null)
                return;
            //Recipe保存后，快照保存后，更新快照
            recipeHistory.SaveDB();
            InitializeRecipeMemory(CurrentRecipe);
        }


        private void UpdateView()
        {

            var isFileSelected = CurrentFileNode != null && CurrentFileNode.IsFile;

            EnableImportExport = true;
            EnableLeftTabPanel = true;
            EnableFilterTreeList = true;
            EnableRefreshRecipeList = true;
            EnableNew = true;

            EnableCellPermButton = isFileSelected;
            EnableReload = isFileSelected;
            EnableNew = isFileSelected;
            EnableReName = isFileSelected;
            EnableCopy = isFileSelected;
            EnableDelete = isFileSelected;
            EnableSave = isFileSelected;
            EnableStep = isFileSelected;
            EnableSaveTo = isFileSelected;
            EnableSaveToAll = isFileSelected;

            if (_editMode == EditMode.None)
            {
                EnableNew = true;
                EnableReName = false;
                EnableCopy = false;
                EnableDelete = false;
                EnableStep = false;
                EnableSave = false;
                EnableReload = true;

            }
            //else if (CurrentRecipe.IsAccessibleWhitelistEditMode)
            else if (_editMode == EditMode.EditWhitelist)
            {
                EnableNew = false;
                EnableReName = false;
                EnableCopy = false;
                EnableDelete = false;
                EnableStep = false;
                EnableSave = false;
                EnableReload = false;
                EnableSaveTo = false;
                EnableSaveToAll = false;
                EnableLeftTabPanel = false;
                EnableFilterTreeList = false;
                EnableRefreshRecipeList = false;
            }

            NotifyOfPropertyChange(nameof(EnableNew));
            NotifyOfPropertyChange(nameof(EnableReName));
            NotifyOfPropertyChange(nameof(EnableCopy));
            NotifyOfPropertyChange(nameof(EnableDelete));
            NotifyOfPropertyChange(nameof(EnableSave));
            NotifyOfPropertyChange(nameof(EnableImportExport));
            NotifyOfPropertyChange(nameof(EnableStep));
            NotifyOfPropertyChange(nameof(EnableSaveTo));
            NotifyOfPropertyChange(nameof(EnableSaveToAll));
            NotifyOfPropertyChange(nameof(EnableLeftTabPanel));
            NotifyOfPropertyChange(nameof(EnableFilterTreeList));
            NotifyOfPropertyChange(nameof(EnableRefreshRecipeList));
            NotifyOfPropertyChange(nameof(EnableCellPermButton));
            NotifyOfPropertyChange(nameof(CurrentRecipe));
        }

        private string _currentCriteria = string.Empty;

        public string CurrentCriteria
        {
            get => _currentCriteria;
            set
            {
                if (value == _currentCriteria)
                    return;

                _currentCriteria = value;
                NotifyOfPropertyChange(nameof(CurrentCriteria));

                ApplyFilter();
            }
        }

        private void ApplyFilter()
        {
            foreach (var node in ProcessTypeFileList[ProcessTypeIndexSelection].FileListByProcessType)
                node.ApplyCriteria(CurrentCriteria, new Stack<FileNode>());
        }

        public void ClearFilter()
        {
            CurrentCriteria = "";
        }

        /// <summary>
        /// 当主窗口状态Login状态发生变化时处理当前窗口状态。
        /// </summary>
        /// <param name="message"></param>
        public void Handle(UserMode message)
        {
            switch (message)
            {
                case UserMode.None:
                    break;
                case UserMode.Normal:
                    break;
                case UserMode.Lock:
                case UserMode.Logoff:
                case UserMode.Exit:
                case UserMode.Shutdown:
                case UserMode.Breakdown:
                    _winValidationInfo?.Close();
                    break;

                default:
                    // ignore
                    break;
            }
        }

        public void Handle(CommonAggregateEvents message)
        {
            switch (message)
            {
                case CommonAggregateEvents.EnterReadOnlyMode:
                    if (CurrentRecipe.IsAccessibleWhitelistEditMode)
                    {
                        CurrentRecipe.LeaveAccessibleWhitelistEditMode().Wait();
                        _editMode = EditMode.Normal;
                    }

                    if (View is RecipeEditorView v)
                        v.editorLocker.Lock();
                    break;
                default:
                    break;
            }
        }
    }
}
