import React, { useEffect, useMemo } from 'react'; import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils'; type Identifier = string | number; type FolderId = Identifier | 'root'; interface UseWorkspaceBreadcrumbsArgs { selectedFolder: FolderId | null; folderNodes: Map; currentFolder: { id?: FolderId; name?: string | null; parentId?: FolderId | null; parent_id?: FolderId | null } | null; breadcrumbFetchRef: React.MutableRefObject>; ensureFolderData: (folderId: FolderId, options?: Record) => Promise; } const useWorkspaceBreadcrumbs = ({ selectedFolder, folderNodes, currentFolder, breadcrumbFetchRef, ensureFolderData, }: UseWorkspaceBreadcrumbsArgs) => { const { breadcrumbs, missingBreadcrumbAncestors } = useMemo(() => { const chain: Array<{ id: FolderId; name?: string | null }> = []; const seen = new Set(); const pending = new Set(); let currentId: FolderId | null = (selectedFolder || 'root') as FolderId; let guard = 0; while (currentId && !seen.has(currentId) && guard < 32) { guard += 1; seen.add(currentId); if (currentId === 'root') { chain.push({ id: 'root', name: DEFAULT_FOLDER_NAME }); currentId = null; break; } const node = folderNodes.get(currentId as FolderId); if (node) { chain.push({ id: currentId, name: node.name || 'Folder' }); currentId = (node.parentId ?? node.parent_id ?? 'root') as FolderId; continue; } let fallbackName: string | null | undefined = '…'; let parentId: FolderId | null | undefined = null; if (currentFolder && currentFolder.id === currentId) { fallbackName = currentFolder.name; parentId = (currentFolder.parent_id ?? currentFolder.parentId ?? 'root') as FolderId; } chain.push({ id: currentId, name: fallbackName }); pending.add(currentId); currentId = parentId as FolderId | null; } if (!chain.some((crumb) => crumb.id === 'root')) { chain.push({ id: 'root', name: DEFAULT_FOLDER_NAME }); } const ordered: Array<{ id: FolderId; name?: string | null }> = []; const seenOrdered = new Set(); chain .slice() .reverse() .forEach((crumb) => { if (!seenOrdered.has(crumb.id)) { seenOrdered.add(crumb.id); ordered.push(crumb); } }); return { breadcrumbs: ordered, missingBreadcrumbAncestors: Array.from(pending) }; }, [selectedFolder, folderNodes, currentFolder]); useEffect(() => { if (!missingBreadcrumbAncestors.length) { return; } missingBreadcrumbAncestors.forEach((folderId) => { if (!folderId || folderId === 'root') { return; } if (breadcrumbFetchRef.current.has(folderId)) { return; } breadcrumbFetchRef.current.add(folderId); ensureFolderData(folderId, { force: false }) .catch((error) => { console.warn('Failed to preload breadcrumb ancestor', folderId, error); }) .finally(() => { breadcrumbFetchRef.current.delete(folderId); }); }); }, [missingBreadcrumbAncestors, ensureFolderData, breadcrumbFetchRef]); return breadcrumbs; }; export default useWorkspaceBreadcrumbs;