/** * ToolWorkflowContext - Manages tool selection, UI state, and workflow coordination * Eliminates prop drilling with a single, simple context */ import React, { createContext, useContext, useReducer, useCallback, useMemo, useEffect } from 'react'; import { useToolManagement } from '@app/hooks/useToolManagement'; import { PageEditorFunctions } from '@app/types/pageEditor'; import { ToolRegistryEntry, ToolRegistry } from '@app/data/toolsTaxonomy'; import { useNavigationActions, useNavigationState } from '@app/contexts/NavigationContext'; import { ToolId, isValidToolId } from '@app/types/toolId'; import { WorkbenchType, getDefaultWorkbench, isBaseWorkbench } from '@app/types/workbench'; import { useNavigationUrlSync } from '@app/hooks/useUrlSync'; import { filterToolRegistryByQuery } from '@app/utils/toolSearch'; import { useToolHistory } from '@app/hooks/tools/useUserToolActivity'; import { ToolWorkflowState, createInitialState, toolWorkflowReducer, } from '@app/contexts/toolWorkflow/toolWorkflowState'; import type { ToolPanelMode } from '@app/constants/toolPanel'; import { usePreferences } from '@app/contexts/PreferencesContext'; import { useToolRegistry } from '@app/contexts/ToolRegistryContext'; // State interface // Types and reducer/state moved to './toolWorkflow/state' // Context value interface export interface CustomWorkbenchViewRegistration { id: string; workbenchId: WorkbenchType; label: string; icon?: React.ReactNode; component: React.ComponentType<{ data: any }>; } export interface CustomWorkbenchViewInstance extends CustomWorkbenchViewRegistration { data: any; } interface ToolWorkflowContextValue extends ToolWorkflowState { // Tool management (from hook) selectedToolKey: ToolId | null; selectedTool: ToolRegistryEntry | null; toolRegistry: Partial; getSelectedTool: (toolId: ToolId | null) => ToolRegistryEntry | null; // UI Actions setSidebarsVisible: (visible: boolean) => void; setLeftPanelView: (view: 'toolPicker' | 'toolContent' | 'hidden') => void; setReaderMode: (mode: boolean) => void; setToolPanelMode: (mode: ToolPanelMode) => void; setPreviewFile: (file: File | null) => void; setPageEditorFunctions: (functions: PageEditorFunctions | null) => void; setSearchQuery: (query: string) => void; selectTool: (toolId: ToolId | null) => void; clearToolSelection: () => void; // Tool Reset Actions toolResetFunctions: Record void>; registerToolReset: (toolId: string, resetFunction: () => void) => void; resetTool: (toolId: string) => void; // Workflow Actions (compound actions) handleToolSelect: (toolId: ToolId) => void; handleBackToTools: () => void; handleReaderToggle: () => void; // Computed values filteredTools: Array<{ item: [ToolId, ToolRegistryEntry]; matchedText?: string }>; // Filtered by search isPanelVisible: boolean; // Tool History favoriteTools: ToolId[]; toggleFavorite: (toolId: ToolId) => void; isFavorite: (toolId: ToolId) => boolean; customWorkbenchViews: CustomWorkbenchViewInstance[]; registerCustomWorkbenchView: (view: CustomWorkbenchViewRegistration) => void; unregisterCustomWorkbenchView: (id: string) => void; setCustomWorkbenchViewData: (id: string, data: any) => void; clearCustomWorkbenchViewData: (id: string) => void; } // Ensure a single context instance across HMR to avoid provider/consumer mismatches const __GLOBAL_CONTEXT_KEY__ = '__ToolWorkflowContext__'; const existingContext = (globalThis as any)[__GLOBAL_CONTEXT_KEY__] as React.Context | undefined; const ToolWorkflowContext = existingContext ?? createContext(undefined); if (!existingContext) { (globalThis as any)[__GLOBAL_CONTEXT_KEY__] = ToolWorkflowContext; } // Provider component interface ToolWorkflowProviderProps { children: React.ReactNode; } export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) { const [state, dispatch] = useReducer(toolWorkflowReducer, undefined, createInitialState); const { preferences, updatePreference } = usePreferences(); // Store reset functions for tools const [toolResetFunctions, setToolResetFunctions] = React.useState void>>({}); const [customViewRegistry, setCustomViewRegistry] = React.useState>({}); const [customViewData, setCustomViewData] = React.useState>({}); // Navigation actions and state are available since we're inside NavigationProvider const { actions } = useNavigationActions(); const navigationState = useNavigationState(); // Tool management hook const { toolRegistry, getSelectedTool } = useToolManagement(); const { allTools } = useToolRegistry(); // Tool history hook const { favoriteTools, toggleFavorite, isFavorite, } = useToolHistory(); // Get selected tool from navigation context const selectedTool = getSelectedTool(navigationState.selectedTool); // UI Action creators const setSidebarsVisible = useCallback((visible: boolean) => { dispatch({ type: 'SET_SIDEBARS_VISIBLE', payload: visible }); }, []); const setLeftPanelView = useCallback((view: 'toolPicker' | 'toolContent' | 'hidden') => { dispatch({ type: 'SET_LEFT_PANEL_VIEW', payload: view }); }, []); const setReaderMode = useCallback((mode: boolean) => { if (mode) { actions.setWorkbench('viewer'); actions.setSelectedTool('read'); } dispatch({ type: 'SET_READER_MODE', payload: mode }); }, [actions]); const setToolPanelMode = useCallback((mode: ToolPanelMode) => { dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: mode }); updatePreference('defaultToolPanelMode', mode); }, [updatePreference]); const setPreviewFile = useCallback((file: File | null) => { dispatch({ type: 'SET_PREVIEW_FILE', payload: file }); if (file) { actions.setWorkbench('viewer'); } }, [actions]); const setPageEditorFunctions = useCallback((functions: PageEditorFunctions | null) => { dispatch({ type: 'SET_PAGE_EDITOR_FUNCTIONS', payload: functions }); }, []); const setSearchQuery = useCallback((query: string) => { dispatch({ type: 'SET_SEARCH_QUERY', payload: query }); }, []); const registerCustomWorkbenchView = useCallback((view: CustomWorkbenchViewRegistration) => { setCustomViewRegistry(prev => ({ ...prev, [view.id]: view })); }, []); const unregisterCustomWorkbenchView = useCallback((id: string) => { let removedView: CustomWorkbenchViewRegistration | undefined; setCustomViewRegistry(prev => { const existing = prev[id]; if (!existing) { return prev; } removedView = existing; const updated = { ...prev }; delete updated[id]; return updated; }); setCustomViewData(prev => { if (!(id in prev)) { return prev; } const updated = { ...prev }; delete updated[id]; return updated; }); if (removedView && navigationState.workbench === removedView.workbenchId) { actions.setWorkbench(getDefaultWorkbench()); } }, [actions, navigationState.workbench]); const setCustomWorkbenchViewData = useCallback((id: string, data: any) => { setCustomViewData(prev => ({ ...prev, [id]: data })); }, []); const clearCustomWorkbenchViewData = useCallback((id: string) => { setCustomViewData(prev => { if (!(id in prev)) { return prev; } const updated = { ...prev }; delete updated[id]; return updated; }); }, []); const customWorkbenchViews = useMemo(() => { return Object.values(customViewRegistry).map(view => ({ ...view, data: Object.prototype.hasOwnProperty.call(customViewData, view.id) ? customViewData[view.id] : null, })); }, [customViewRegistry, customViewData]); useEffect(() => { if (isBaseWorkbench(navigationState.workbench)) { return; } const currentCustomView = customWorkbenchViews.find(view => view.workbenchId === navigationState.workbench); if (!currentCustomView || currentCustomView.data == null) { actions.setWorkbench(getDefaultWorkbench()); } }, [actions, customWorkbenchViews, navigationState.workbench]); // Persisted via PreferencesContext; no direct localStorage writes needed here // Keep tool panel mode in sync with user preference. This ensures the // Config setting (Default tool picker mode) immediately affects the app // and persists across reloads. useEffect(() => { const preferredMode = preferences.defaultToolPanelMode; if (preferredMode !== state.toolPanelMode) { dispatch({ type: 'SET_TOOL_PANEL_MODE', payload: preferredMode }); } }, [preferences.defaultToolPanelMode, state.toolPanelMode]); // Tool reset methods const registerToolReset = useCallback((toolId: string, resetFunction: () => void) => { setToolResetFunctions(prev => ({ ...prev, [toolId]: resetFunction })); }, []); const resetTool = useCallback((toolId: string) => { // Use the current state directly instead of depending on the state in the closure setToolResetFunctions(current => { const resetFunction = current[toolId]; if (resetFunction) { resetFunction(); } return current; // Return the same state to avoid unnecessary updates }); }, []); // Empty dependency array makes this stable // Workflow actions (compound actions that coordinate multiple state changes) const handleToolSelect = useCallback((toolId: ToolId) => { // If we're currently on a custom workbench (e.g., Validate Signature report), // selecting any tool should take the user back to the default file manager view. const wasInCustomWorkbench = !isBaseWorkbench(navigationState.workbench); // Handle read tool selection - should behave exactly like QuickAccessBar read button if (toolId === 'read') { setReaderMode(true); actions.setSelectedTool('read'); actions.setWorkbench(wasInCustomWorkbench ? getDefaultWorkbench() : 'viewer'); setSearchQuery(''); return; } // Handle multiTool selection - enable page editor workbench if (toolId === 'multiTool') { setReaderMode(false); setLeftPanelView('hidden'); actions.setSelectedTool('multiTool'); actions.setWorkbench(wasInCustomWorkbench ? getDefaultWorkbench() : 'pageEditor'); setSearchQuery(''); return; } // Set the selected tool and determine the appropriate workbench const validToolId = isValidToolId(toolId) ? toolId : null; actions.setSelectedTool(validToolId); // Get the tool from registry to determine workbench const tool = getSelectedTool(toolId); if (wasInCustomWorkbench) { actions.setWorkbench(getDefaultWorkbench()); } else if (tool && tool.workbench) { actions.setWorkbench(tool.workbench); } else { actions.setWorkbench(getDefaultWorkbench()); } // Clear search query when selecting a tool setSearchQuery(''); setLeftPanelView('toolContent'); setReaderMode(false); // Disable read mode when selecting tools }, [actions, getSelectedTool, navigationState.workbench, setLeftPanelView, setReaderMode, setSearchQuery]); const handleBackToTools = useCallback(() => { setLeftPanelView('toolPicker'); setReaderMode(false); actions.setSelectedTool(null); }, [setLeftPanelView, setReaderMode, actions.setSelectedTool]); const handleReaderToggle = useCallback(() => { setReaderMode(true); }, [setReaderMode]); // Filter tools based on search query with fuzzy matching (name, description, id, synonyms) const filteredTools = useMemo(() => { if (!toolRegistry) return []; return filterToolRegistryByQuery(toolRegistry, state.searchQuery); }, [toolRegistry, state.searchQuery]); const isPanelVisible = useMemo(() => state.sidebarsVisible && !state.readerMode && state.leftPanelView !== 'hidden', [state.sidebarsVisible, state.readerMode, state.leftPanelView] ); useNavigationUrlSync( navigationState.selectedTool, handleToolSelect, handleBackToTools, allTools, true ); // Properly memoized context value const contextValue = useMemo((): ToolWorkflowContextValue => ({ // State ...state, selectedToolKey: navigationState.selectedTool, selectedTool, toolRegistry, getSelectedTool, // Actions setSidebarsVisible, setLeftPanelView, setReaderMode, setToolPanelMode, setPreviewFile, setPageEditorFunctions, setSearchQuery, selectTool: actions.setSelectedTool, clearToolSelection: () => actions.setSelectedTool(null), // Tool Reset Actions toolResetFunctions, registerToolReset, resetTool, // Workflow Actions handleToolSelect, handleBackToTools, handleReaderToggle, // Computed filteredTools, isPanelVisible, // Tool History favoriteTools, toggleFavorite, isFavorite, // Custom workbench views customWorkbenchViews, registerCustomWorkbenchView, unregisterCustomWorkbenchView, setCustomWorkbenchViewData, clearCustomWorkbenchViewData, }), [ state, navigationState.selectedTool, selectedTool, toolRegistry, getSelectedTool, setSidebarsVisible, setLeftPanelView, setReaderMode, setToolPanelMode, setPreviewFile, setPageEditorFunctions, setSearchQuery, actions.setSelectedTool, registerToolReset, resetTool, handleToolSelect, handleBackToTools, handleReaderToggle, filteredTools, isPanelVisible, favoriteTools, toggleFavorite, isFavorite, customWorkbenchViews, registerCustomWorkbenchView, unregisterCustomWorkbenchView, setCustomWorkbenchViewData, clearCustomWorkbenchViewData, ]); return ( {children} ); } // Custom hook to use the context export function useToolWorkflow(): ToolWorkflowContextValue { const context = useContext(ToolWorkflowContext); if (!context) { console.error('ToolWorkflowContext not found. Current stack:', new Error().stack); throw new Error('useToolWorkflow must be used within a ToolWorkflowProvider'); } return context; }