import type { PractitionerRole } from 'fhir/r4'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useAuthServiceContext } from '../services/AuthService'; import { usePractitionerRoleServiceContext, type ExtendedPractitionerRole } from '../services/PractitionerRoleService'; import { environment } from '../environments/environment'; import { viewerEpoch } from '../utils/writeThroughHold'; import { isClinicianEnabled } from '../utils/clinicianEnabled'; import { SaveRetiredError, useSaveScope, type SaveOperation } from '../utils/saveOperation'; import { isProtectedNonclinicalRole, clinicianSeatReferences, clinicianSeatScopeDigest, clinicianSeatIntentDigest, ClinicianSeatReferenceUnavailable, type ClinicianSeatCreateReference, type ClinicianSeatClientScope, type ClinicianSeatCreateResult, type KeyedClinicianSeatCreateResult } from '../utils/clinicianSeatCreate'; export interface ClinicianSeatSave extends SaveOperation { clientScope: ClinicianSeatClientScope; isCurrentScope(): boolean } interface Options { active: boolean; practitionerId: string | undefined; roleId?: string; editor: string; references?: ReturnType; } export const useClinicianSeatCreate = (options: Options) => { const auth = useAuthServiceContext(); const services = usePractitionerRoleServiceContext(); const current = useRef({ options, auth }); current.current = { options, auth }; const readScope = (): ClinicianSeatClientScope => { const { auth: viewer, options: shown } = current.current; return { account: viewer.currentUser?.cognito?.sub ?? '', actor: `${viewer.currentUser?.fhir?.resourceType ?? ''}/${viewer.currentUser?.fhir?.id ?? ''}`, pool: environment.cognitoUserPoolId, deployment: environment.fhirIdentityApi, organizationId: viewer.wpOrganization?.current?.id ?? '', practitionerId: shown.practitionerId ?? '', relatedPersonId: viewer.actingAsRef?.current?.relatedPersonId ?? '', subjectPatientId: viewer.actingAsRef?.current?.subjectPatientId ?? '' }; }; const keyNow = () => JSON.stringify([current.current.options.active, current.current.options.roleId, current.current.options.editor, current.current.auth.actingAsSessionRef?.current ?? 0, readScope()]); const ownerKey = keyNow(); const publication = useRef({ key: ownerKey, generation: 0, mounted: true }); if (publication.current.key !== ownerKey) { publication.current.key = ownerKey; publication.current.generation += 1; } useEffect(() => { publication.current.mounted = true; return () => { publication.current.mounted = false; publication.current.generation += 1; }; }, []); const lifetime = useSaveScope(ownerKey); const records = useMemo(() => { if (options.references) return options.references; try { const navigation = performance.getEntriesByType?.('navigation')[0] as PerformanceNavigationTiming | undefined; return clinicianSeatReferences(window.sessionStorage, navigation?.type !== 'reload'); } catch { return clinicianSeatReferences(undefined); } }, [options.references]); const payload = useRef<{ key: string; operationId: string; body: ExtendedPractitionerRole }>(); if (payload.current && payload.current.key !== JSON.stringify(readScope())) payload.current = undefined; const [shown, setShown] = useState<{ key: string; reference?: ClinicianSeatCreateReference; latest?: KeyedClinicianSeatCreateResult; unavailable?: boolean }>(); const beginSave = useCallback((): ClinicianSeatSave => { lifetime.renew(); const base = lifetime.start(); const startedAs = keyNow(), clientScope = readScope(); const generation = ++publication.current.generation, epoch = viewerEpoch(); const isCurrentScope = () => publication.current.mounted && publication.current.generation === generation && viewerEpoch() === epoch && current.current.options.active && keyNow() === startedAs; const belongs = () => base.belongs() && isCurrentScope(); const admit = () => { base.admit(); if (!belongs()) { base.retire(); throw new SaveRetiredError(); } }; return { ...base, clientScope, isCurrentScope, belongs, admit, transport: { ...base.transport, admit }, step: async (work: () => Promise, write?: boolean): Promise => { admit(); try { const result = await base.step(work, write); admit(); return result; } catch (error) { admit(); throw error; } } }; }, [lifetime]); useEffect(() => { if (!options.active) return; const owner = beginSave(); void (async () => { try { const digest = await owner.step(() => clinicianSeatScopeDigest(owner.clientScope)); owner.admit(); const reference = records.held(digest); owner.admit(); setShown({ key: ownerKey, ...(reference && (!options.roleId || reference.roleId === options.roleId) ? { reference } : {}) }); } catch { if (owner.belongs()) setShown({ key: ownerKey, unavailable: true }); } finally { owner.retire(); } })(); return () => owner.retire(); }, [ownerKey, options.active, options.roleId, beginSave, records]); const create = async (command: ExtendedPractitionerRole, owner: ClinicianSeatSave, mode: 'check' | 'resume' = 'resume'): Promise => { owner.admit(); const body = JSON.parse(JSON.stringify(command)) as ExtendedPractitionerRole; if (body.practitioner.id !== owner.clientScope.practitionerId || body.practitionerRole.practitioner?.reference !== `Practitioner/${owner.clientScope.practitionerId}` || body.practitionerRole.organization?.reference !== `Organization/${owner.clientScope.organizationId}`) throw new ClinicianSeatReferenceUnavailable(); const flagged = isClinicianEnabled(body.practitionerRole); const protectedCreate = flagged || isProtectedNonclinicalRole(body.practitionerRole, body.userGroup); let digest: string; let reference: ClinicianSeatCreateReference | undefined; try { digest = await clinicianSeatScopeDigest(owner.clientScope); owner.admit(); reference = records.held(digest); } catch (error) { owner.admit(); if (protectedCreate || shown?.key === ownerKey && shown.reference || mode === 'check') throw error; const role = await services.createPractitionerRole(body, owner.transport); owner.admit(); return { keyed: false, standing: 'ordinary', role }; } if (!protectedCreate && !reference) { if (mode === 'check') throw new ClinicianSeatReferenceUnavailable(); const role = await services.createPractitionerRole(body, owner.transport); owner.admit(); return { keyed: false, standing: 'ordinary', role }; } body.practitioner = { resourceType: 'Practitioner', id: body.practitioner.id }; const intent = await clinicianSeatIntentDigest(body); owner.admit(); if (reference && reference.intent !== intent) throw new Error('Re-enter the original role details to check or resume this creation.'); if (!reference && mode === 'check') throw new ClinicianSeatReferenceUnavailable(); reference ??= records.reserve(digest, intent, () => crypto.randomUUID().toLowerCase()); owner.admit(); payload.current = { key: JSON.stringify(owner.clientScope), operationId: reference.operationId, body }; setShown({ key: ownerKey, reference }); const result = await services.createClinicianPractitionerRole(body, { operationId: reference.operationId, organizationId: owner.clientScope.organizationId, practitionerId: owner.clientScope.practitionerId, purpose: flagged ? 'clinical' : 'nonclinical', ...(reference.roleId ? { roleId: reference.roleId } : {}), mode, transport: owner.transport }); owner.admit(); reference = records.bindRole(reference, result.roleId, Boolean(result.role)); if (result.standing === 'enabled' || result.standing === 'created') { records.settle(reference); payload.current = undefined; reference = undefined; } owner.admit(); setShown({ key: ownerKey, reference, latest: result }); return result; }; const observeEnabledRole = async (role: PractitionerRole, owner: ClinicianSeatSave): Promise => { owner.admit(); if (!role.id || !role.meta?.versionId || !isClinicianEnabled(role) || role.active === false || role.practitioner?.reference !== `Practitioner/${owner.clientScope.practitionerId}` || role.organization?.reference !== `Organization/${owner.clientScope.organizationId}`) return; const digest = await owner.step(() => clinicianSeatScopeDigest(owner.clientScope)); owner.admit(); const reference = records.held(digest); if (reference?.roleId !== role.id) return; records.settle(reference); payload.current = undefined; owner.admit(); setShown({ key: ownerKey }); }; const retained = shown?.key === ownerKey ? shown.reference : undefined; return { ownerKey, beginSave, create, observeEnabledRole, retire: lifetime.renew, retained, latest: shown?.key === ownerKey ? shown.latest : undefined, discovering: options.active && shown?.key !== ownerKey, unavailable: shown?.key === ownerKey && shown.unavailable === true, needsReentry: Boolean(retained && payload.current?.operationId !== retained.operationId), }; };