using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;

namespace SourceGit.Views
{
    public class RevisionFileTreeNodeToggleButton : ToggleButton
    {
        protected override Type StyleKeyOverride => typeof(ToggleButton);

        protected override async void OnPointerPressed(PointerPressedEventArgs e)
        {
            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
                DataContext is ViewModels.RevisionFileTreeNode { IsFolder: true } node)
            {
                var tree = this.FindAncestorOfType<RevisionFileTreeView>();
                if (tree != null)
                    await tree.ToggleNodeIsExpandedAsync(node);
            }

            e.Handled = true;
        }
    }

    public class RevisionTreeNodeIcon : UserControl
    {
        public static readonly StyledProperty<ViewModels.RevisionFileTreeNode> NodeProperty =
            AvaloniaProperty.Register<RevisionTreeNodeIcon, ViewModels.RevisionFileTreeNode>(nameof(Node));

        public ViewModels.RevisionFileTreeNode Node
        {
            get => GetValue(NodeProperty);
            set => SetValue(NodeProperty, value);
        }

        public static readonly StyledProperty<bool> IsExpandedProperty =
            AvaloniaProperty.Register<RevisionTreeNodeIcon, bool>(nameof(IsExpanded));

        public bool IsExpanded
        {
            get => GetValue(IsExpandedProperty);
            set => SetValue(IsExpandedProperty, value);
        }

        static RevisionTreeNodeIcon()
        {
            NodeProperty.Changed.AddClassHandler<RevisionTreeNodeIcon>((icon, _) => icon.UpdateContent());
            IsExpandedProperty.Changed.AddClassHandler<RevisionTreeNodeIcon>((icon, _) => icon.UpdateContent());
        }

        private void UpdateContent()
        {
            var node = Node;
            if (node?.Backend == null)
            {
                Content = null;
                return;
            }

            var obj = node.Backend;
            switch (obj.Type)
            {
                case Models.ObjectType.Blob:
                    CreateContent("Icons.File", new Thickness(0, 0, 0, 0));
                    break;
                case Models.ObjectType.Commit:
                    CreateContent("Icons.Submodule", new Thickness(0, 0, 0, 0));
                    break;
                default:
                    CreateContent(node.IsExpanded ? "Icons.Folder.Open" : "Icons.Folder", new Thickness(0, 2, 0, 0), Brushes.Goldenrod);
                    break;
            }
        }

        private void CreateContent(string iconKey, Thickness margin, IBrush fill = null)
        {
            if (this.FindResource(iconKey) is not StreamGeometry geo)
                return;

            var icon = new Avalonia.Controls.Shapes.Path()
            {
                Width = 14,
                Height = 14,
                Margin = margin,
                Stretch = Stretch.Uniform,
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Center,
                Data = geo,
            };

            if (fill != null)
                icon.Fill = fill;

            Content = icon;
        }
    }

    public class RevisionFileRowsListBox : ListBox
    {
        protected override Type StyleKeyOverride => typeof(ListBox);

        protected override async void OnKeyDown(KeyEventArgs e)
        {
            if (SelectedItem is ViewModels.RevisionFileTreeNode node)
            {
                if (node.IsFolder &&
                    e.KeyModifiers == KeyModifiers.None &&
                    (node.IsExpanded && e.Key == Key.Left) || (!node.IsExpanded && e.Key == Key.Right))
                {
                    var tree = this.FindAncestorOfType<RevisionFileTreeView>();
                    if (tree != null)
                        await tree.ToggleNodeIsExpandedAsync(node);
                    e.Handled = true;
                }
                else if (e.Key == Key.C &&
                    e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
                {
                    var detailView = this.FindAncestorOfType<CommitDetail>();
                    if (detailView is { DataContext: ViewModels.CommitDetail detail })
                    {
                        var path = node.Backend?.Path ?? string.Empty;
                        if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
                            path = detail.GetAbsPath(path);

                        await App.CopyTextAsync(path);
                        e.Handled = true;
                    }
                }
                else if (node.Backend is { Type: Models.ObjectType.Blob } file &&
                    e.Key == Key.S &&
                    e.KeyModifiers == ((OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) | KeyModifiers.Shift))
                {
                    var detailView = this.FindAncestorOfType<CommitDetail>();
                    if (detailView is { DataContext: ViewModels.CommitDetail detail })
                    {
                        var storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
                        if (storageProvider == null)
                            return;

                        var options = new FolderPickerOpenOptions() { AllowMultiple = false };
                        try
                        {
                            var selected = await storageProvider.OpenFolderPickerAsync(options);
                            if (selected.Count == 1)
                            {
                                var folder = selected[0];
                                var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder.Path.ToString();
                                var saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!);
                                await detail.SaveRevisionFileAsync(file, saveTo);
                            }
                        }
                        catch (Exception ex)
                        {
                            App.RaiseException(detail.Repository.FullPath, $"Failed to save file: {ex.Message}");
                        }

                        e.Handled = true;
                    }
                }
            }

            if (!e.Handled)
                base.OnKeyDown(e);
        }
    }

    public partial class RevisionFileTreeView : UserControl
    {
        public static readonly StyledProperty<string> RevisionProperty =
            AvaloniaProperty.Register<RevisionFileTreeView, string>(nameof(Revision));

        public string Revision
        {
            get => GetValue(RevisionProperty);
            set => SetValue(RevisionProperty, value);
        }

        public AvaloniaList<ViewModels.RevisionFileTreeNode> Rows { get; } = [];

        public RevisionFileTreeView()
        {
            InitializeComponent();
        }

        public async Task SetSearchResultAsync(string file)
        {
            Rows.Clear();
            _searchResult.Clear();

            var rows = new List<ViewModels.RevisionFileTreeNode>();
            if (string.IsNullOrEmpty(file))
            {
                MakeRows(rows, _tree, 0);
            }
            else
            {
                var vm = DataContext as ViewModels.CommitDetail;
                if (vm?.Commit == null)
                    return;

                var objects = await vm.GetRevisionFilesUnderFolderAsync(file);
                if (objects is not { Count: 1 })
                    return;

                var routes = file.Split('/');
                if (routes.Length == 1)
                {
                    _searchResult.Add(new ViewModels.RevisionFileTreeNode
                    {
                        Backend = objects[0]
                    });
                }
                else
                {
                    var last = _searchResult;
                    var prefix = string.Empty;
                    for (var i = 0; i < routes.Length - 1; i++)
                    {
                        var folder = new ViewModels.RevisionFileTreeNode
                        {
                            Backend = new Models.Object
                            {
                                Type = Models.ObjectType.Tree,
                                Path = prefix + routes[i],
                            },
                            IsExpanded = true,
                        };

                        last.Add(folder);
                        last = folder.Children;
                        prefix = folder.Backend + "/";
                    }

                    last.Add(new ViewModels.RevisionFileTreeNode
                    {
                        Backend = objects[0]
                    });
                }

                MakeRows(rows, _searchResult, 0);
            }

            Rows.AddRange(rows);
            GC.Collect();
        }

        public async Task ToggleNodeIsExpandedAsync(ViewModels.RevisionFileTreeNode node)
        {
            _disableSelectionChangingEvent = true;
            node.IsExpanded = !node.IsExpanded;

            var depth = node.Depth;
            var idx = Rows.IndexOf(node);
            if (idx == -1)
                return;

            if (node.IsExpanded)
            {
                var subtree = await GetChildrenOfTreeNodeAsync(node);
                if (subtree is { Count: > 0 })
                {
                    var subrows = new List<ViewModels.RevisionFileTreeNode>();
                    MakeRows(subrows, subtree, depth + 1);
                    Rows.InsertRange(idx + 1, subrows);
                }
            }
            else
            {
                var removeCount = 0;
                for (int i = idx + 1; i < Rows.Count; i++)
                {
                    var row = Rows[i];
                    if (row.Depth <= depth)
                        break;

                    removeCount++;
                }
                Rows.RemoveRange(idx + 1, removeCount);
            }

            _disableSelectionChangingEvent = false;
        }

        protected override async void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
        {
            base.OnPropertyChanged(change);

            if (change.Property == RevisionProperty)
            {
                _tree.Clear();
                _searchResult.Clear();

                if (DataContext is ViewModels.CommitDetail { ActiveTabIndex: 2 } vm)
                    await ReloadTreeData(vm);
                else
                    Rows.Clear();
            }
        }

        protected override async void OnLoaded(RoutedEventArgs e)
        {
            base.OnLoaded(e);

            if (DataContext is ViewModels.CommitDetail vm && _tree.Count == 0)
                await ReloadTreeData(vm);
        }

        private void OnTreeNodeContextRequested(object sender, ContextRequestedEventArgs e)
        {
            if (DataContext is ViewModels.CommitDetail { Repository: { } repo, Commit: { } commit } vm &&
                sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { Backend: { } obj } } grid)
            {
                var menu = obj.Type switch
                {
                    Models.ObjectType.Tree => CreateRevisionFileContextMenuByFolder(repo, vm, commit, obj.Path),
                    _ => CreateRevisionFileContextMenu(repo, vm, commit, obj),
                };
                menu.Open(grid);
            }

            e.Handled = true;
        }

        private async void OnTreeNodeDoubleTapped(object sender, TappedEventArgs e)
        {
            if (sender is Grid { DataContext: ViewModels.RevisionFileTreeNode { IsFolder: true } node })
            {
                var posX = e.GetPosition(this).X;
                if (posX < node.Depth * 16 + 16)
                    return;

                await ToggleNodeIsExpandedAsync(node);
            }
        }

        private async void OnRowsSelectionChanged(object sender, SelectionChangedEventArgs _)
        {
            if (_disableSelectionChangingEvent || DataContext is not ViewModels.CommitDetail vm)
                return;

            if (sender is ListBox { SelectedItem: ViewModels.RevisionFileTreeNode { IsFolder: false } node })
                await vm.ViewRevisionFileAsync(node.Backend);
            else
                await vm.ViewRevisionFileAsync(null);
        }

        private async Task<List<ViewModels.RevisionFileTreeNode>> GetChildrenOfTreeNodeAsync(ViewModels.RevisionFileTreeNode node)
        {
            if (!node.IsFolder)
                return null;

            if (node.Children.Count > 0)
                return node.Children;

            if (DataContext is not ViewModels.CommitDetail vm)
                return null;

            var objects = await vm.GetRevisionFilesUnderFolderAsync(node.Backend.Path + "/");
            if (objects == null || objects.Count == 0)
                return null;

            foreach (var obj in objects)
                node.Children.Add(new ViewModels.RevisionFileTreeNode() { Backend = obj });

            SortNodes(node.Children);
            return node.Children;
        }

        private void MakeRows(List<ViewModels.RevisionFileTreeNode> rows, List<ViewModels.RevisionFileTreeNode> nodes, int depth)
        {
            foreach (var node in nodes)
            {
                node.Depth = depth;
                rows.Add(node);

                if (!node.IsExpanded || !node.IsFolder)
                    continue;

                MakeRows(rows, node.Children, depth + 1);
            }
        }

        private void SortNodes(List<ViewModels.RevisionFileTreeNode> nodes)
        {
            nodes.Sort((l, r) =>
            {
                if (l.IsFolder == r.IsFolder)
                    return Models.NumericSort.Compare(l.Name, r.Name);
                return l.IsFolder ? -1 : 1;
            });
        }

        private async Task ReloadTreeData(ViewModels.CommitDetail vm)
        {
            if (_isReloadingTreeData)
                return;

            _isReloadingTreeData = true;

            if (vm?.Commit == null)
            {
                Rows.Clear();
                _isReloadingTreeData = false;
                return;
            }

            var objects = await vm.GetRevisionFilesUnderFolderAsync(null);
            if (objects == null || objects.Count == 0)
            {
                Rows.Clear();
                _isReloadingTreeData = false;
                return;
            }

            foreach (var obj in objects)
                _tree.Add(new ViewModels.RevisionFileTreeNode { Backend = obj });

            SortNodes(_tree);

            var topTree = new List<ViewModels.RevisionFileTreeNode>();
            MakeRows(topTree, _tree, 0);

            Rows.Clear();
            Rows.AddRange(topTree);
            _isReloadingTreeData = false;
        }

        private ContextMenu CreateRevisionFileContextMenuByFolder(ViewModels.Repository repo, ViewModels.CommitDetail vm, Models.Commit commit, string path)
        {
            var fullPath = Native.OS.GetAbsPath(repo.FullPath, path);
            var explore = new MenuItem();
            explore.Header = App.Text("RevealFile");
            explore.Icon = App.CreateMenuIcon("Icons.Explore");
            explore.IsEnabled = Directory.Exists(fullPath);
            explore.Click += (_, ev) =>
            {
                Native.OS.OpenInFileManager(fullPath, true);
                ev.Handled = true;
            };

            var history = new MenuItem();
            history.Header = App.Text("DirHistories");
            history.Icon = App.CreateMenuIcon("Icons.Histories");
            history.Click += (_, ev) =>
            {
                App.ShowWindow(new ViewModels.DirHistories(repo, path, commit.SHA));
                ev.Handled = true;
            };

            var copyPath = new MenuItem();
            copyPath.Header = App.Text("CopyPath");
            copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
            copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
            copyPath.Click += async (_, ev) =>
            {
                await App.CopyTextAsync(path);
                ev.Handled = true;
            };

            var copyFullPath = new MenuItem();
            copyFullPath.Header = App.Text("CopyFullPath");
            copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
            copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
            copyFullPath.Click += async (_, e) =>
            {
                await App.CopyTextAsync(fullPath);
                e.Handled = true;
            };

            var menu = new ContextMenu();
            menu.Items.Add(explore);
            menu.Items.Add(new MenuItem() { Header = "-" });
            menu.Items.Add(history);
            menu.Items.Add(new MenuItem() { Header = "-" });
            menu.Items.Add(copyPath);
            menu.Items.Add(copyFullPath);
            return menu;
        }

        private ContextMenu CreateRevisionFileContextMenu(ViewModels.Repository repo, ViewModels.CommitDetail vm, Models.Commit commit, Models.Object file)
        {
            var fullPath = Native.OS.GetAbsPath(repo.FullPath, file.Path);
            var menu = new ContextMenu();

            var openWith = new MenuItem();
            openWith.Header = App.Text("Open");
            openWith.Icon = App.CreateMenuIcon("Icons.OpenWith");
            openWith.IsEnabled = file.Type == Models.ObjectType.Blob;
            if (openWith.IsEnabled)
            {
                var defaultEditor = new MenuItem();
                defaultEditor.Header = App.Text("Open.SystemDefaultEditor");
                defaultEditor.Tag = OperatingSystem.IsMacOS() ? "⌘+O" : "Ctrl+O";
                defaultEditor.Click += async (_, ev) =>
                {
                    await vm.OpenRevisionFileAsync(file.Path, null);
                    ev.Handled = true;
                };

                openWith.Items.Add(defaultEditor);

                var tools = Native.OS.ExternalTools;
                if (tools.Count > 0)
                {
                    openWith.Items.Add(new MenuItem() { Header = "-" });

                    for (var i = 0; i < tools.Count; i++)
                    {
                        var tool = tools[i];
                        var item = new MenuItem();
                        item.Header = tool.Name;
                        item.Icon = new Image { Width = 16, Height = 16, Source = tool.IconImage };
                        item.Click += async (_, ev) =>
                        {
                            await vm.OpenRevisionFileAsync(file.Path, tool);
                            ev.Handled = true;
                        };

                        openWith.Items.Add(item);
                    }
                }
            }

            var saveAs = new MenuItem();
            saveAs.Header = App.Text("SaveAs");
            saveAs.Icon = App.CreateMenuIcon("Icons.Save");
            saveAs.IsEnabled = file.Type == Models.ObjectType.Blob;
            saveAs.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+S" : "Ctrl+Shift+S";
            saveAs.Click += async (_, ev) =>
            {
                var storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
                if (storageProvider == null)
                    return;

                var options = new FolderPickerOpenOptions() { AllowMultiple = false };
                try
                {
                    var selected = await storageProvider.OpenFolderPickerAsync(options);
                    if (selected.Count == 1)
                    {
                        var folder = selected[0];
                        var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder.Path.ToString();
                        var saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)!);
                        await vm.SaveRevisionFileAsync(file, saveTo);
                    }
                }
                catch (Exception e)
                {
                    App.RaiseException(repo.FullPath, $"Failed to save file: {e.Message}");
                }

                ev.Handled = true;
            };

            var explore = new MenuItem();
            explore.Header = App.Text("RevealFile");
            explore.Icon = App.CreateMenuIcon("Icons.Explore");
            explore.IsEnabled = File.Exists(fullPath);
            explore.Click += (_, ev) =>
            {
                Native.OS.OpenInFileManager(fullPath, file.Type == Models.ObjectType.Blob);
                ev.Handled = true;
            };

            menu.Items.Add(openWith);
            menu.Items.Add(saveAs);
            menu.Items.Add(explore);
            menu.Items.Add(new MenuItem() { Header = "-" });

            var history = new MenuItem();
            history.Header = App.Text("FileHistory");
            history.Icon = App.CreateMenuIcon("Icons.Histories");
            history.Click += (_, ev) =>
            {
                App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA));
                ev.Handled = true;
            };

            var blame = new MenuItem();
            blame.Header = App.Text("Blame");
            blame.Icon = App.CreateMenuIcon("Icons.Blame");
            blame.IsEnabled = file.Type == Models.ObjectType.Blob;
            blame.Click += (_, ev) =>
            {
                App.ShowWindow(new ViewModels.Blame(repo.FullPath, file.Path, commit));
                ev.Handled = true;
            };

            menu.Items.Add(history);
            menu.Items.Add(blame);
            menu.Items.Add(new MenuItem() { Header = "-" });

            if (!repo.IsBare)
            {
                var resetToThisRevision = new MenuItem();
                resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
                resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
                resetToThisRevision.Click += async (_, ev) =>
                {
                    await vm.ResetToThisRevisionAsync(file.Path);
                    ev.Handled = true;
                };

                var change = vm.Changes.Find(x => x.Path == file.Path) ?? new Models.Change() { Index = Models.ChangeState.None, Path = file.Path };
                var resetToFirstParent = new MenuItem();
                resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
                resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
                resetToFirstParent.IsEnabled = commit.Parents.Count > 0;
                resetToFirstParent.Click += async (_, ev) =>
                {
                    await vm.ResetToParentRevisionAsync(change);
                    ev.Handled = true;
                };

                menu.Items.Add(resetToThisRevision);
                menu.Items.Add(resetToFirstParent);
                menu.Items.Add(new MenuItem() { Header = "-" });

                if (repo.Remotes.Count > 0 && File.Exists(fullPath) && repo.IsLFSEnabled())
                {
                    var lfs = new MenuItem();
                    lfs.Header = App.Text("GitLFS");
                    lfs.Icon = App.CreateMenuIcon("Icons.LFS");

                    var lfsLock = new MenuItem();
                    lfsLock.Header = App.Text("GitLFS.Locks.Lock");
                    lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
                    if (repo.Remotes.Count == 1)
                    {
                        lfsLock.Click += async (_, e) =>
                        {
                            await repo.LockLFSFileAsync(repo.Remotes[0].Name, change.Path);
                            e.Handled = true;
                        };
                    }
                    else
                    {
                        foreach (var remote in repo.Remotes)
                        {
                            var remoteName = remote.Name;
                            var lockRemote = new MenuItem();
                            lockRemote.Header = remoteName;
                            lockRemote.Click += async (_, e) =>
                            {
                                await repo.LockLFSFileAsync(remoteName, change.Path);
                                e.Handled = true;
                            };
                            lfsLock.Items.Add(lockRemote);
                        }
                    }
                    lfs.Items.Add(lfsLock);

                    var lfsUnlock = new MenuItem();
                    lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
                    lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
                    if (repo.Remotes.Count == 1)
                    {
                        lfsUnlock.Click += async (_, e) =>
                        {
                            await repo.UnlockLFSFileAsync(repo.Remotes[0].Name, change.Path, false, true);
                            e.Handled = true;
                        };
                    }
                    else
                    {
                        foreach (var remote in repo.Remotes)
                        {
                            var remoteName = remote.Name;
                            var unlockRemote = new MenuItem();
                            unlockRemote.Header = remoteName;
                            unlockRemote.Click += async (_, e) =>
                            {
                                await repo.UnlockLFSFileAsync(remoteName, change.Path, false, true);
                                e.Handled = true;
                            };
                            lfsUnlock.Items.Add(unlockRemote);
                        }
                    }
                    lfs.Items.Add(lfsUnlock);

                    menu.Items.Add(lfs);
                    menu.Items.Add(new MenuItem() { Header = "-" });
                }
            }

            var actions = repo.GetCustomActions(Models.CustomActionScope.File);
            if (actions.Count > 0)
            {
                var target = new Models.CustomActionTargetFile(file.Path, vm.Commit);
                var custom = new MenuItem();
                custom.Header = App.Text("FileCM.CustomAction");
                custom.Icon = App.CreateMenuIcon("Icons.Action");

                foreach (var action in actions)
                {
                    var (dup, label) = action;
                    var item = new MenuItem();
                    item.Icon = App.CreateMenuIcon("Icons.Action");
                    item.Header = label;
                    item.Click += async (_, e) =>
                    {
                        await repo.ExecCustomActionAsync(dup, target);
                        e.Handled = true;
                    };

                    custom.Items.Add(item);
                }

                menu.Items.Add(custom);
                menu.Items.Add(new MenuItem() { Header = "-" });
            }

            var copyPath = new MenuItem();
            copyPath.Header = App.Text("CopyPath");
            copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
            copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
            copyPath.Click += async (_, ev) =>
            {
                await App.CopyTextAsync(file.Path);
                ev.Handled = true;
            };

            var copyFullPath = new MenuItem();
            copyFullPath.Header = App.Text("CopyFullPath");
            copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
            copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
            copyFullPath.Click += async (_, e) =>
            {
                await App.CopyTextAsync(fullPath);
                e.Handled = true;
            };

            menu.Items.Add(copyPath);
            menu.Items.Add(copyFullPath);
            return menu;
        }

        private List<ViewModels.RevisionFileTreeNode> _tree = [];
        private bool _disableSelectionChangingEvent = false;
        private List<ViewModels.RevisionFileTreeNode> _searchResult = [];
        private bool _isReloadingTreeData = false;
    }
}
