import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState, } from 'react'; import DocumentsManager from '../../documents/DocumentsManager'; type DocumentId = string | number; interface DocumentLike { id?: DocumentId; [key: string]: unknown; } interface FolderContentsEntry { documents?: DocumentLike[]; [key: string]: unknown; } interface UseDocumentsOptions { setFolderContents: Dispatch>>; fetchDocumentById?: (id: DocumentId) => Promise; } const useDocuments = ({ setFolderContents, fetchDocumentById, }: UseDocumentsOptions) => { const managerRef = useRef( new DocumentsManager(fetchDocumentById), ); const [documents, setDocumentsState] = useState([]); useEffect(() => { managerRef.current.setFetcher(fetchDocumentById); }, [fetchDocumentById]); const setDocuments = useCallback( (value: DocumentLike[] | ((prev: DocumentLike[]) => DocumentLike[])) => { setDocumentsState((prev) => { const resolved = typeof value === 'function' ? value(prev) : value; if (!Array.isArray(resolved)) { return resolved; } const { canonical } = managerRef.current.ingest(resolved); return canonical; }); }, [], ); const mapDocumentCaches = useCallback( (mapper: (doc: DocumentLike) => DocumentLike | undefined) => { managerRef.current.map(mapper); const lookupSnapshot = managerRef.current.getSnapshot(); setDocumentsState((prev) => { if (!Array.isArray(prev) || prev.length === 0) { return prev; } let changed = false; const next = prev.map((doc) => { const id = doc?.id; if (id != null && lookupSnapshot.has(id as DocumentId)) { const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike; if (canonical !== doc) { changed = true; } return canonical; } const updated = mapper(doc); const nextDoc = updated === undefined ? doc : updated; if (nextDoc !== doc) { changed = true; } return nextDoc; }); return changed ? next : prev; }); setFolderContents((prev) => { if (!prev.size) { return prev; } let changed = false; const next = new Map(); prev.forEach((contents, key) => { const docs = Array.isArray(contents?.documents) ? contents.documents : null; if (!docs || docs.length === 0) { next.set(key, contents); return; } let docsChanged = false; const updatedDocs = docs.map((doc) => { const id = doc?.id; if (id != null && lookupSnapshot.has(id as DocumentId)) { const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike; if (canonical !== doc) { docsChanged = true; } return canonical; } const updated = mapper(doc); const nextDoc = updated === undefined ? doc : updated; if (nextDoc !== doc) { docsChanged = true; } return nextDoc; }); if (docsChanged) { changed = true; next.set(key, { ...contents, documents: updatedDocs }); } else { next.set(key, contents); } }); return changed ? next : prev; }); }, [setFolderContents], ); const updateDocumentCaches = useCallback( (documentId, updater) => { if (!documentId) { return; } mapDocumentCaches((doc) => { if (!doc || doc.id !== documentId) { return doc; } const updated = updater(doc); return updated === undefined ? doc : updated; }); }, [mapDocumentCaches], ); const removeDocumentsFromLookup = useCallback( (documentIds: Array) => { if (!Array.isArray(documentIds) || !documentIds.length) { return; } managerRef.current.remove(documentIds); }, [], ); return { documents, setDocuments, removeDocumentsFromLookup, mapDocumentCaches, updateDocumentCaches, documentsManager: managerRef.current, }; }; export default useDocuments;