import { createContext, useContext, useMemo, type ReactNode } from 'react' import type { ResourceDefinition, ResourceContextValue, Identifier } from '../types' // ============================================================================= // Single Resource Context (for individual resource pages) // ============================================================================= export const ResourceContext = createContext(null) export interface ResourceProviderProps { children: ReactNode /** Resource name */ resource: string /** Resource definition */ definition?: ResourceDefinition /** Current record ID (if viewing/editing) */ id?: Identifier } /** * Provides resource context for a single resource view. * * @example * ```tsx * * * * ``` */ export function ResourceProvider({ children, resource, definition, id, }: ResourceProviderProps) { const value = useMemo(() => ({ resource, definition, id, hasList: !!definition?.list, hasCreate: !!definition?.create, hasEdit: !!definition?.edit, hasShow: !!definition?.show, }), [resource, definition, id]) return ( {children} ) } /** * Hook to access the current resource context. * Must be used within a ResourceProvider. * * @example * ```tsx * const { resource, id, hasList, hasEdit } = useResource() * ``` */ export function useResource(): ResourceContextValue { const context = useContext(ResourceContext) if (!context) { throw new Error('useResource must be used within a ResourceProvider') } return context } // ============================================================================= // Resources Collection Context (for app-wide resource registry) // ============================================================================= export interface ResourcesContextValue { /** All registered resources */ resources: ResourceDefinition[] /** Get a resource by name */ getResource: (name: string) => ResourceDefinition | undefined /** Register a new resource */ registerResource: (resource: ResourceDefinition) => void /** Unregister a resource */ unregisterResource: (name: string) => void } export const ResourcesContext = createContext(null) export interface ResourcesProviderProps { children: ReactNode /** Initial resources to register */ resources?: ResourceDefinition[] } /** * Provides a registry of all resources in the application. * * @example * ```tsx * * * * ``` */ export function ResourcesProvider({ children, resources: initialResources = [], }: ResourcesProviderProps) { // Use a ref-based state to allow imperative updates without re-renders const resourcesRef = useMemo(() => { const map = new Map() initialResources.forEach((r) => map.set(r.name, r)) return map }, []) // Empty deps - we want this to be stable // Initialize from props on mount useMemo(() => { initialResources.forEach((r) => resourcesRef.set(r.name, r)) }, [initialResources, resourcesRef]) const value = useMemo(() => ({ get resources() { return Array.from(resourcesRef.values()) }, getResource: (name: string) => resourcesRef.get(name), registerResource: (resource: ResourceDefinition) => { resourcesRef.set(resource.name, resource) }, unregisterResource: (name: string) => { resourcesRef.delete(name) }, }), [resourcesRef]) return ( {children} ) } /** * Hook to access the resources registry. * Must be used within a ResourcesProvider. * * @example * ```tsx * const { resources, getResource } = useResources() * const usersResource = getResource('users') * ``` */ export function useResources(): ResourcesContextValue { const context = useContext(ResourcesContext) if (!context) { throw new Error('useResources must be used within a ResourcesProvider') } return context }