import type { FolderTreeNode } from "@/lib/sidebar-nav-maps" /** Slug URL-safe pour un segment de chemin (dossier / partie de libellé). */ export function slugifyNavSegment(name: string): string { const s = name .trim() .toLowerCase() .normalize("NFD") .replace(/\p{M}/gu, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, "") return s || "dossier" } export function folderIdFromPathSlugs(slugs: string[]): string { return `folder-${slugs.join("-")}` } export function findFolderPath( nodes: FolderTreeNode[], id: string, acc: FolderTreeNode[] = [] ): FolderTreeNode[] | null { for (const n of nodes) { const next = [...acc, n] if (n.id === id) return next if (n.children?.length) { const hit = findFolderPath(n.children, id, next) if (hit) return hit } } return null } export function collectFolderIdsInTree(nodes: FolderTreeNode[]): string[] { const out: string[] = [] const walk = (ns: FolderTreeNode[]) => { for (const e of ns) { out.push(e.id) if (e.children?.length) walk(e.children) } } walk(nodes) return out } function collectSubtreeIdsFromNode(node: FolderTreeNode): Set { const s = new Set([node.id]) if (node.children?.length) { for (const c of node.children) { for (const x of collectSubtreeIdsFromNode(c)) s.add(x) } } return s } export function uniqueFolderPathId( pathSlugs: string[], usedIds: Set ): string { let base = folderIdFromPathSlugs(pathSlugs) if (!usedIds.has(base)) return base let n = 2 while (usedIds.has(`${base}-${n}`)) n += 1 return `${base}-${n}` } /** Slugs des ancêtres (noms affichés) pour un parent `parentId` dans l’arbre. */ export function ancestorLabelSlugsForParentId( tree: FolderTreeNode[], parentId: string | null ): string[] { if (parentId === null) return [] const path = findFolderPath(tree, parentId) if (!path) return [] return path.map((n) => slugifyNavSegment(n.label)) } export function proposeNewFolderId( tree: FolderTreeNode[], labelRowIds: readonly string[], parentId: string | null, displayLabel: string ): string { const used = new Set([ ...collectFolderIdsInTree(tree), ...labelRowIds, ]) const anc = ancestorLabelSlugsForParentId(tree, parentId) const leaf = slugifyNavSegment(displayLabel) return uniqueFolderPathId([...anc, leaf], used) } /** * Recalcule les ids du sous-arbre enraciné à `rootId` (chemins slug à partir des libellés). * `tree` doit déjà contenir les bons libellés pour ce sous-arbre. */ export function rekeyFolderSubtreeAt( tree: FolderTreeNode[], rootId: string, labelRowIds: readonly string[] ): { tree: FolderTreeNode[]; idMap: Record } | null { const path = findFolderPath(tree, rootId) if (!path) return null const rootNode = path[path.length - 1]! const ancestorSlugs = path.slice(0, -1).map((n) => slugifyNavSegment(n.label)) const subtreeOldIds = collectSubtreeIdsFromNode(rootNode) const used = new Set([...labelRowIds]) for (const id of collectFolderIdsInTree(tree)) { if (!subtreeOldIds.has(id)) used.add(id) } const idMap: Record = {} function rebuild(node: FolderTreeNode, anc: string[]): FolderTreeNode { const pathSlugs = [...anc, slugifyNavSegment(node.label)] const newId = uniqueFolderPathId(pathSlugs, used) used.add(newId) if (newId !== node.id) idMap[node.id] = newId const kids = node.children?.length ? node.children.map((c) => rebuild(c, pathSlugs)) : undefined return { ...node, id: newId, children: kids } } const rebuiltRoot = rebuild(rootNode, ancestorSlugs) function replaceAt(nodes: FolderTreeNode[]): FolderTreeNode[] { return nodes.map((n) => { if (n.id === rootId) return rebuiltRoot if (n.children?.length) return { ...n, children: replaceAt(n.children) } return n }) } return { tree: replaceAt(tree), idMap } }