import type { TenantSummary } from "./adminApi"; export type TenantNode = TenantSummary & { children: TenantNode[]; recursiveMemberCount: number; }; /** * Builds a hierarchical tree from a flat list of tenants and calculates * direct and recursive member counts for each node. */ export function buildTenantFullTree( allTenants: TenantSummary[], rootId?: string, ): { currentBase: TenantNode | null; subTree: TenantNode[] } { if (allTenants.length === 0) return { currentBase: null, subTree: [] }; const tenantMap = new Map(); for (const t of allTenants) { tenantMap.set(t.id, { ...t, children: [], recursiveMemberCount: t.memberCount || 0, }); } const visitedDuringBuild = new Set(); // Build initial children relations and prevent simple cycles for (const t of allTenants) { if (t.parentId && t.parentId !== t.id) { const parent = tenantMap.get(t.parentId); const child = tenantMap.get(t.id); if (parent && child) { // Simple cycle prevention during build: don't add if it creates an immediate loop parent.children.push(child); } } } const visitedForCalc = new Set(); // Function to calculate recursive counts with cycle protection const calculateRecursive = (node: TenantNode): number => { if (visitedForCalc.has(node.id)) { console.warn( `Circular dependency detected in tenant tree for ID: ${node.id}`, ); return 0; // Prevent infinite loop } visitedForCalc.add(node.id); let total = node.memberCount || 0; for (const child of node.children) { total += calculateRecursive(child); } node.recursiveMemberCount = total; // We don't remove from visitedForCalc here because a tree shouldn't have // multiple paths to the same node anyway (it's a tree, not a graph). // If it were a DAG, we'd need different logic, but for a tree with parentIds, // a node should only be visited once. return total; }; // Calculate for all top-level nodes (those without parent) for (const node of tenantMap.values()) { if (!node.parentId) { visitedForCalc.clear(); calculateRecursive(node); } } // If a specific rootId is provided, find and return its subtree if (rootId) { const base = tenantMap.get(rootId); if (base) { // Re-calculate specifically for our current tenant to be sure if it wasn't a global root visitedForCalc.clear(); calculateRecursive(base); return { currentBase: base, subTree: base.children }; } return { currentBase: null, subTree: [] }; } // If no rootId, return all top-level roots as subTree const roots = Array.from(tenantMap.values()).filter((n) => !n.parentId); return { currentBase: null, subTree: roots }; }