//-----------------------------------------------------------------------
//
// 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);
}
}
}