//----------------------------------------------------------------------- // // Copyright (c) aliasvault. All rights reserved. // Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. // //----------------------------------------------------------------------- namespace AliasVault.Client.Main.Utilities; using AliasClientDb; using AliasVault.Client.Main.Models; /// /// Utilities for working with folder hierarchies and trees. /// public static class FolderTreeUtilities { /// /// Maximum allowed folder nesting depth. /// Structure: Root (0) > Level 1 (1) > Level 2 (2) > Level 3 (3) > Level 4 (4). /// Folders at depth 4 cannot have subfolders. /// public const int MaxFolderDepth = 4; /// /// Build a hierarchical tree from a flat array of folders. /// /// Flat array of folders. /// Array of root-level folder tree nodes. public static List BuildFolderTree(IEnumerable folders) { var folderList = folders.ToList(); // Create a map for quick lookup var folderMap = new Dictionary(); // Initialize all folders as tree nodes foreach (var folder in folderList) { folderMap[folder.Id] = new FolderTreeNode { Folder = folder, Children = new List(), Depth = 0, Path = new List(), }; } // Build the tree structure var rootFolders = new List(); foreach (var folder in folderList) { var node = folderMap[folder.Id]; if (folder.ParentFolderId == null) { // Root folder node.Depth = 0; node.Path = new List { folder.Id }; rootFolders.Add(node); } else { // Child folder if (folderMap.TryGetValue(folder.ParentFolderId.Value, out var parent)) { node.Depth = parent.Depth + 1; node.Path = new List(parent.Path) { folder.Id }; parent.Children.Add(node); } else { // Parent not found or deleted - treat as root node.Depth = 0; node.Path = new List { folder.Id }; rootFolders.Add(node); } } } // Sort children recursively SortChildren(rootFolders); return rootFolders; } /// /// Get folder depth in the hierarchy. /// /// The folder ID to check. /// Flat array of all folders. /// Depth (0 = root, 1 = one level deep, etc.) or null if folder not found. public static int? GetFolderDepth(Guid folderId, IEnumerable folders) { var folderList = folders.ToList(); var folder = folderList.FirstOrDefault(f => f.Id == folderId); if (folder == null) { return null; } int depth = 0; Guid? currentId = folderId; // Traverse up to root, counting levels while (currentId.HasValue) { var current = folderList.FirstOrDefault(f => f.Id == currentId.Value); if (current == null || current.ParentFolderId == null) { break; } depth++; currentId = current.ParentFolderId; // Prevent infinite loops if (depth > MaxFolderDepth) { break; } } return depth; } /// /// Get the full path of folder names from root to the specified folder. /// /// The folder ID. /// Flat array of all folders. /// Array of folder names from root to current folder, or empty array if not found. public static List GetFolderPath(Guid? folderId, IEnumerable folders) { if (!folderId.HasValue) { return new List(); } var path = new List(); var folderList = folders.ToList(); Guid? currentId = folderId; int iterations = 0; // Build path by traversing up to root while (currentId.HasValue && iterations < MaxFolderDepth + 1) { var folder = folderList.FirstOrDefault(f => f.Id == currentId.Value); if (folder == null) { break; } path.Insert(0, folder.Name); // Add to beginning of array currentId = folder.ParentFolderId; iterations++; } return path; } /// /// Get the full path of folder IDs from root to the specified folder. /// /// The folder ID. /// Flat array of all folders. /// Array of folder IDs from root to current folder, or empty array if not found. public static List GetFolderIdPath(Guid? folderId, IEnumerable folders) { if (!folderId.HasValue) { return new List(); } var path = new List(); var folderList = folders.ToList(); Guid? currentId = folderId; int iterations = 0; // Build path by traversing up to root while (currentId.HasValue && iterations < MaxFolderDepth + 1) { var folder = folderList.FirstOrDefault(f => f.Id == currentId.Value); if (folder == null) { break; } path.Insert(0, folder.Id); // Add to beginning of array currentId = folder.ParentFolderId; iterations++; } return path; } /// /// Format folder path for display with separator. /// /// Array of folder names. /// Separator string (default: " > "). /// Formatted folder path string. public static string FormatFolderPath(List pathSegments, string separator = " > ") { return string.Join(separator, pathSegments); } /// /// Flatten a folder tree into a sorted array suitable for dropdowns. /// Includes visual indentation in the name. /// /// Root-level folder tree nodes. /// Optional folder ID to exclude (useful when moving folders). /// Flat array of tree nodes with indented names. public static List FlattenFolderTree( List tree, Guid? excludeId = null) { var result = new List(); void Traverse(List nodes) { foreach (var node in nodes) { if (excludeId.HasValue && node.Folder.Id == excludeId.Value) { continue; // Skip excluded folder and its children } result.Add(node); Traverse(node.Children); } } Traverse(tree); return result; } /// /// Check if a folder can have subfolders (not at max depth). /// /// The folder ID to check. /// Flat array of all folders. /// True if folder can have children, false otherwise. public static bool CanHaveSubfolders(Guid folderId, IEnumerable folders) { var depth = GetFolderDepth(folderId, folders); return depth.HasValue && depth.Value < MaxFolderDepth; } /// /// Get all descendant folder IDs (children, grandchildren, etc.). /// /// The parent folder ID. /// Flat array of all folders. /// Array of descendant folder IDs. public static List GetDescendantFolderIds(Guid folderId, IEnumerable folders) { var descendants = new List(); var folderList = folders.ToList(); void Traverse(Guid parentId) { var children = folderList.Where(f => f.ParentFolderId == parentId).ToList(); foreach (var child in children) { descendants.Add(child.Id); Traverse(child.Id); } } Traverse(folderId); return descendants; } /// /// Get all direct child folder IDs. /// /// The parent folder ID (null for root). /// Flat array of all folders. /// Array of direct child folder IDs. public static List GetDirectChildFolderIds(Guid? parentFolderId, IEnumerable folders) { return folders .Where(f => f.ParentFolderId == parentFolderId) .Select(f => f.Id) .ToList(); } /// /// Sort children of a folder tree node recursively. /// private static void SortChildren(List nodes) { nodes.Sort((a, b) => { // Sort by weight first, then by name (case-insensitive) if (a.Folder.Weight != b.Folder.Weight) { return a.Folder.Weight.CompareTo(b.Folder.Weight); } return string.Compare(a.Folder.Name, b.Folder.Name, StringComparison.OrdinalIgnoreCase); }); foreach (var node in nodes) { SortChildren(node.Children); } } }