"use client" import { createContext, useCallback, useContext, useEffect, useMemo, useRef, type ReactNode, } from "react" import type { FolderTreeNode, LabelRowItem } from "@/lib/sidebar-nav-data" import { useNavStore, type LabelListSidebarVisibility, type LabelInMessageListVisibility, type NavItemPrefs, } from "@/lib/stores/nav-store" import { buildEmailLabelToSidebarFolderId, buildFolderIdToLabelRecord, } from "@/lib/sidebar-nav-maps" export type { LabelListSidebarVisibility, LabelInMessageListVisibility, NavItemPrefs } export type NavEmailSync = { renameLabel: (from: string, to: string) => void removeLabel: (label: string) => void } const navEmailSyncRef = { current: null as NavEmailSync | null } export function registerNavEmailSync(cb: NavEmailSync | null) { navEmailSyncRef.current = cb } type SidebarNavContextValue = { folderTree: FolderTreeNode[] labelRows: LabelRowItem[] folderIdToLabel: Record emailLabelToSidebarFolderId: Record getNavItemPrefs: (id: string) => Required> setNavItemSidebarVisibility: (id: string, v: LabelListSidebarVisibility) => void setNavItemMessageVisibility: (id: string, v: LabelInMessageListVisibility) => void ensureLabelRowForLabelText: (label: string) => void addLabelRowFromSidebar: (label: string) => void addFolder: (parentId: string | null, name: string) => void updateFolderOrLabelColor: (id: string, color: string) => void renameFolderOrLabel: (id: string, newLabel: string) => void removeFolderOrLabelRow: (id: string) => void moveFolder: (id: string, newParentId: string | null) => void addSubfolder: (parentId: string, name: string) => void addChildLabelRow: (parentLabelRowId: string, childName: string) => void } const SidebarNavContext = createContext(null) type SidebarNavProviderProps = { children: ReactNode routeFolderId?: string | null onRouteFolderIdChange?: (nextFolderId: string) => void } export function SidebarNavProvider({ children, routeFolderId, onRouteFolderIdChange, }: SidebarNavProviderProps) { const routeSyncRef = useRef({ routeFolderId: null as string | null | undefined, onRouteFolderIdChange: undefined as | ((nextFolderId: string) => void) | undefined, }) useEffect(() => { routeSyncRef.current = { routeFolderId, onRouteFolderIdChange } }, [routeFolderId, onRouteFolderIdChange]) const scheduleRouteFolderIdSync = useCallback( (idMap: Record) => { if (Object.keys(idMap).length === 0) return queueMicrotask(() => { const { routeFolderId: rid, onRouteFolderIdChange: on } = routeSyncRef.current if (!rid || !on) return const next = idMap[rid] if (next) on(next) }) }, [] ) const folderTree = useNavStore((s) => s.folderTree) const labelRows = useNavStore((s) => s.labelRows) const navItemPrefs = useNavStore((s) => s.navItemPrefs) const navActions = useNavStore.getState() const folderIdToLabel = useMemo( () => buildFolderIdToLabelRecord(folderTree, labelRows), [folderTree, labelRows] ) const emailLabelToSidebarFolderId = useMemo( () => buildEmailLabelToSidebarFolderId(folderIdToLabel), [folderIdToLabel] ) const getNavItemPrefs = useCallback( (id: string): Required> => { const p = navItemPrefs[id] return { sidebar: p?.sidebar ?? "show", messages: p?.messages ?? "show", } }, [navItemPrefs] ) const renameFolderOrLabel = useCallback( (id: string, newLabel: string) => { const { idMap, emailRename } = useNavStore.getState().renameFolderOrLabel(id, newLabel) scheduleRouteFolderIdSync(idMap) if (emailRename) { queueMicrotask(() => { navEmailSyncRef.current?.renameLabel(emailRename.from, emailRename.to) }) } }, [scheduleRouteFolderIdSync] ) const removeFolderOrLabelRow = useCallback( (id: string) => { const labelsToRemove = useNavStore.getState().removeFolderOrLabelRow(id) if (labelsToRemove.length > 0) { queueMicrotask(() => { for (const lab of labelsToRemove) { navEmailSyncRef.current?.removeLabel(lab) } }) } }, [] ) const moveFolder = useCallback( (id: string, newParentId: string | null) => { const idMap = useNavStore.getState().moveFolder(id, newParentId) scheduleRouteFolderIdSync(idMap) }, [scheduleRouteFolderIdSync] ) const value = useMemo( () => ({ folderTree, labelRows, folderIdToLabel, emailLabelToSidebarFolderId, getNavItemPrefs, setNavItemSidebarVisibility: navActions.setNavItemSidebarVisibility, setNavItemMessageVisibility: navActions.setNavItemMessageVisibility, ensureLabelRowForLabelText: navActions.ensureLabelRowForLabelText, addLabelRowFromSidebar: navActions.addLabelRowFromSidebar, addFolder: navActions.addFolder, updateFolderOrLabelColor: navActions.updateFolderOrLabelColor, renameFolderOrLabel, removeFolderOrLabelRow, moveFolder, addSubfolder: navActions.addSubfolder, addChildLabelRow: navActions.addChildLabelRow, }), [ folderTree, labelRows, folderIdToLabel, emailLabelToSidebarFolderId, getNavItemPrefs, navActions, renameFolderOrLabel, removeFolderOrLabelRow, moveFolder, ] ) return ( {children} ) } export function useSidebarNav(): SidebarNavContextValue { const ctx = useContext(SidebarNavContext) if (!ctx) { throw new Error("useSidebarNav must be used within SidebarNavProvider") } return ctx } export function folderMoveParentOptions( tree: FolderTreeNode[], excludeId: string ): { value: string; label: string; depth: number }[] { function findNode(nodes: FolderTreeNode[], id: string): FolderTreeNode | null { for (const n of nodes) { if (n.id === id) return n if (n.children?.length) { const hit = findNode(n.children, id) if (hit) return hit } } return null } function collectIds(node: FolderTreeNode): Set { const s = new Set([node.id]) if (node.children?.length) { for (const c of node.children) { for (const x of collectIds(c)) s.add(x) } } return s } const ex = findNode(tree, excludeId) const banned = ex ? collectIds(ex) : new Set([excludeId]) const out: { value: string; label: string; depth: number }[] = [ { value: "__root__", label: "Racine", depth: 0 }, ] const walk = (nodes: FolderTreeNode[], depth: number) => { for (const n of nodes) { if (banned.has(n.id)) continue out.push({ value: n.id, label: `${"\u2003".repeat(depth * 2)}${n.label}`, depth, }) if (n.children?.length) walk(n.children, depth + 1) } } walk(tree, 0) return out }