import { useCallback, useEffect, useRef, useState } from 'react' import { taskVersionIsAfter } from './document-model' export type AuxiliaryTaskReadGuard = ReturnType type RefetchDocuments = (options: { cancelRefetch: true }) => Promise<{ error: unknown }> export function createAuxiliaryTaskReadGuard() { const blockedVersions = new Map() return { block(taskId: string, taskVersion: string) { const blockedVersion = blockedVersions.get(taskId) if (blockedVersion && taskVersionIsAfter(blockedVersion, taskVersion)) return blockedVersions.set(taskId, taskVersion) }, clear() { blockedVersions.clear() }, clearTask(taskId: string) { blockedVersions.delete(taskId) }, clearThrough(taskId: string, authoritativeVersion: string) { const blockedVersion = blockedVersions.get(taskId) if (!blockedVersion || taskVersionIsAfter(blockedVersion, authoritativeVersion)) return false blockedVersions.delete(taskId) return true }, isBlocked(taskId: string, taskVersion: string) { const blockedVersion = blockedVersions.get(taskId) return Boolean(blockedVersion && !taskVersionIsAfter(taskVersion, blockedVersion)) }, retain(taskIds: Set) { for (const taskId of blockedVersions.keys()) { if (!taskIds.has(taskId)) blockedVersions.delete(taskId) } }, } } export function useAuxiliaryTaskReadGuard({ documentPermissionDenied, refetchDocuments, }: { documentPermissionDenied: boolean refetchDocuments: RefetchDocuments }) { const guardRef = useRef(null) if (!guardRef.current) guardRef.current = createAuxiliaryTaskReadGuard() const guard = guardRef.current const previousDocumentPermissionDeniedRef = useRef(documentPermissionDenied) const denialGenerationRef = useRef(0) const [permissionDenied, setPermissionDenied] = useState(false) const [guardRevision, setGuardRevision] = useState(0) useEffect(() => { const wasDenied = previousDocumentPermissionDeniedRef.current previousDocumentPermissionDeniedRef.current = documentPermissionDenied if (!wasDenied || documentPermissionDenied) return guard.clear() // oxlint-disable-next-line eslint-react/set-state-in-effect -- The authoritative document permission transition retires the local denial. setPermissionDenied(false) // oxlint-disable-next-line eslint-react/set-state-in-effect -- Guard mutations need one render so blocked readers are reconsidered. setGuardRevision((current) => current + 1) }, [documentPermissionDenied, guard]) const retry = useCallback(() => { const denialGeneration = denialGenerationRef.current + 1 denialGenerationRef.current = denialGeneration setPermissionDenied(true) void refetchDocuments({ cancelRefetch: true }).then( (result) => { if (denialGenerationRef.current === denialGeneration && !result.error) setPermissionDenied(false) }, () => undefined, ) }, [refetchDocuments]) const deny = useCallback( (taskId: string, taskVersion: string) => { guard.block(taskId, taskVersion) retry() }, [guard, retry], ) return { deny, guard, guardRevision, permissionDenied, retry } }