'use client' // ============================================================================= // TOM Generator Provider // Context provider component for the TOM Generator Wizard // ============================================================================= import React, { createContext, useReducer, useCallback, useEffect, useRef, ReactNode, } from 'react' import { TOMGeneratorState, TOMGeneratorStepId, CompanyProfile, DataProfile, ArchitectureProfile, SecurityProfile, RiskProfile, EvidenceDocument, DerivedTOM, ExportRecord, WizardStep, createInitialTOMGeneratorState, TOM_GENERATOR_STEPS, getStepIndex, } from './types' import { TOMRulesEngine } from './rules-engine' import { tomGeneratorReducer, TOMGeneratorAction } from './reducer' // ============================================================================= // CONTEXT VALUE INTERFACE // ============================================================================= export interface TOMGeneratorContextValue { state: TOMGeneratorState dispatch: React.Dispatch // Navigation currentStepIndex: number totalSteps: number canGoNext: boolean canGoPrevious: boolean goToStep: (stepId: TOMGeneratorStepId) => void goToNextStep: () => void goToPreviousStep: () => void completeCurrentStep: (data: unknown) => void // Profile setters setCompanyProfile: (profile: CompanyProfile) => void updateCompanyProfile: (data: Partial) => void setDataProfile: (profile: DataProfile) => void updateDataProfile: (data: Partial) => void setArchitectureProfile: (profile: ArchitectureProfile) => void updateArchitectureProfile: (data: Partial) => void setSecurityProfile: (profile: SecurityProfile) => void updateSecurityProfile: (data: Partial) => void setRiskProfile: (profile: RiskProfile) => void updateRiskProfile: (data: Partial) => void // Evidence management addEvidence: (document: EvidenceDocument) => void updateEvidence: (id: string, data: Partial) => void deleteEvidence: (id: string) => void // TOM derivation deriveTOMs: () => void updateDerivedTOM: (id: string, data: Partial) => void bulkUpdateTOMs: (updates: Array<{ id: string; data: Partial }>) => void // Gap analysis runGapAnalysis: () => void // Export addExport: (record: ExportRecord) => void // Persistence saveState: () => Promise loadState: () => Promise resetState: () => void // Status isStepCompleted: (stepId: TOMGeneratorStepId) => boolean getCompletionPercentage: () => number isLoading: boolean error: string | null } // ============================================================================= // CONTEXT // ============================================================================= export const TOMGeneratorContext = createContext(null) // ============================================================================= // STORAGE KEYS // ============================================================================= const STORAGE_KEY_PREFIX = 'tom-generator-state-' function getStorageKey(tenantId: string): string { return `${STORAGE_KEY_PREFIX}${tenantId}` } // ============================================================================= // PROVIDER COMPONENT // ============================================================================= interface TOMGeneratorProviderProps { children: ReactNode tenantId: string initialState?: TOMGeneratorState enablePersistence?: boolean } export function TOMGeneratorProvider({ children, tenantId, initialState, enablePersistence = true, }: TOMGeneratorProviderProps) { const [state, dispatch] = useReducer( tomGeneratorReducer, initialState ?? createInitialTOMGeneratorState(tenantId) ) const [isLoading, setIsLoading] = React.useState(false) const [error, setError] = React.useState(null) const rulesEngineRef = useRef(null) // Initialize rules engine useEffect(() => { if (!rulesEngineRef.current) { rulesEngineRef.current = new TOMRulesEngine() } }, []) // Load state from localStorage on mount useEffect(() => { if (enablePersistence && typeof window !== 'undefined') { try { const stored = localStorage.getItem(getStorageKey(tenantId)) if (stored) { const parsed = JSON.parse(stored) if (parsed.createdAt) parsed.createdAt = new Date(parsed.createdAt) if (parsed.updatedAt) parsed.updatedAt = new Date(parsed.updatedAt) if (parsed.steps) { parsed.steps = parsed.steps.map((step: WizardStep) => ({ ...step, validatedAt: step.validatedAt ? new Date(step.validatedAt) : null, })) } if (parsed.documents) { parsed.documents = parsed.documents.map((doc: EvidenceDocument) => ({ ...doc, uploadedAt: new Date(doc.uploadedAt), validFrom: doc.validFrom ? new Date(doc.validFrom) : null, validUntil: doc.validUntil ? new Date(doc.validUntil) : null, aiAnalysis: doc.aiAnalysis ? { ...doc.aiAnalysis, analyzedAt: new Date(doc.aiAnalysis.analyzedAt), } : null, })) } if (parsed.derivedTOMs) { parsed.derivedTOMs = parsed.derivedTOMs.map((tom: DerivedTOM) => ({ ...tom, implementationDate: tom.implementationDate ? new Date(tom.implementationDate) : null, reviewDate: tom.reviewDate ? new Date(tom.reviewDate) : null, })) } if (parsed.gapAnalysis?.generatedAt) { parsed.gapAnalysis.generatedAt = new Date(parsed.gapAnalysis.generatedAt) } if (parsed.exports) { parsed.exports = parsed.exports.map((exp: ExportRecord) => ({ ...exp, generatedAt: new Date(exp.generatedAt), })) } dispatch({ type: 'LOAD_STATE', payload: parsed }) } } catch (e) { console.error('Failed to load TOM Generator state from localStorage:', e) } } }, [tenantId, enablePersistence]) // Save state to localStorage on changes useEffect(() => { if (enablePersistence && typeof window !== 'undefined') { try { localStorage.setItem(getStorageKey(tenantId), JSON.stringify(state)) } catch (e) { console.error('Failed to save TOM Generator state to localStorage:', e) } } }, [state, tenantId, enablePersistence]) // Navigation helpers const currentStepIndex = getStepIndex(state.currentStep) const totalSteps = TOM_GENERATOR_STEPS.length const canGoNext = currentStepIndex < totalSteps - 1 const canGoPrevious = currentStepIndex > 0 const goToStep = useCallback((stepId: TOMGeneratorStepId) => { dispatch({ type: 'SET_CURRENT_STEP', payload: stepId }) }, []) const goToNextStep = useCallback(() => { if (canGoNext) { const nextStep = TOM_GENERATOR_STEPS[currentStepIndex + 1] dispatch({ type: 'SET_CURRENT_STEP', payload: nextStep.id }) } }, [canGoNext, currentStepIndex]) const goToPreviousStep = useCallback(() => { if (canGoPrevious) { const prevStep = TOM_GENERATOR_STEPS[currentStepIndex - 1] dispatch({ type: 'SET_CURRENT_STEP', payload: prevStep.id }) } }, [canGoPrevious, currentStepIndex]) const completeCurrentStep = useCallback( (data: unknown) => { dispatch({ type: 'COMPLETE_STEP', payload: { stepId: state.currentStep, data }, }) }, [state.currentStep] ) // Profile setters const setCompanyProfile = useCallback((profile: CompanyProfile) => { dispatch({ type: 'SET_COMPANY_PROFILE', payload: profile }) }, []) const updateCompanyProfile = useCallback((data: Partial) => { dispatch({ type: 'UPDATE_COMPANY_PROFILE', payload: data }) }, []) const setDataProfile = useCallback((profile: DataProfile) => { dispatch({ type: 'SET_DATA_PROFILE', payload: profile }) }, []) const updateDataProfile = useCallback((data: Partial) => { dispatch({ type: 'UPDATE_DATA_PROFILE', payload: data }) }, []) const setArchitectureProfile = useCallback((profile: ArchitectureProfile) => { dispatch({ type: 'SET_ARCHITECTURE_PROFILE', payload: profile }) }, []) const updateArchitectureProfile = useCallback( (data: Partial) => { dispatch({ type: 'UPDATE_ARCHITECTURE_PROFILE', payload: data }) }, [] ) const setSecurityProfile = useCallback((profile: SecurityProfile) => { dispatch({ type: 'SET_SECURITY_PROFILE', payload: profile }) }, []) const updateSecurityProfile = useCallback((data: Partial) => { dispatch({ type: 'UPDATE_SECURITY_PROFILE', payload: data }) }, []) const setRiskProfile = useCallback((profile: RiskProfile) => { dispatch({ type: 'SET_RISK_PROFILE', payload: profile }) }, []) const updateRiskProfile = useCallback((data: Partial) => { dispatch({ type: 'UPDATE_RISK_PROFILE', payload: data }) }, []) // Evidence management const addEvidence = useCallback((document: EvidenceDocument) => { dispatch({ type: 'ADD_EVIDENCE', payload: document }) }, []) const updateEvidence = useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, data } }) }, [] ) const deleteEvidence = useCallback((id: string) => { dispatch({ type: 'DELETE_EVIDENCE', payload: id }) }, []) // TOM derivation const deriveTOMs = useCallback(() => { if (!rulesEngineRef.current) return const derivedTOMs = rulesEngineRef.current.deriveAllTOMs({ companyProfile: state.companyProfile, dataProfile: state.dataProfile, architectureProfile: state.architectureProfile, securityProfile: state.securityProfile, riskProfile: state.riskProfile, }) dispatch({ type: 'SET_DERIVED_TOMS', payload: derivedTOMs }) }, [ state.companyProfile, state.dataProfile, state.architectureProfile, state.securityProfile, state.riskProfile, ]) const updateDerivedTOM = useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_DERIVED_TOM', payload: { id, data } }) }, [] ) const bulkUpdateTOMs = useCallback( (updates: Array<{ id: string; data: Partial }>) => { for (const { id, data } of updates) { dispatch({ type: 'UPDATE_DERIVED_TOM', payload: { id, data } }) } }, [] ) // Gap analysis const runGapAnalysis = useCallback(() => { if (!rulesEngineRef.current) return const result = rulesEngineRef.current.performGapAnalysis( state.derivedTOMs, state.documents ) dispatch({ type: 'SET_GAP_ANALYSIS', payload: result }) }, [state.derivedTOMs, state.documents]) // Export const addExport = useCallback((record: ExportRecord) => { dispatch({ type: 'ADD_EXPORT', payload: record }) }, []) // Persistence const saveState = useCallback(async () => { setIsLoading(true) setError(null) try { const response = await fetch('/api/sdk/v1/tom-generator/state', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenantId, state }), }) if (!response.ok) { throw new Error('Failed to save state') } } catch (e) { setError(e instanceof Error ? e.message : 'Unknown error') throw e } finally { setIsLoading(false) } }, [tenantId, state]) const loadState = useCallback(async () => { setIsLoading(true) setError(null) try { const response = await fetch( `/api/sdk/v1/tom-generator/state?tenantId=${tenantId}` ) if (!response.ok) { throw new Error('Failed to load state') } const data = await response.json() if (data.state) { dispatch({ type: 'LOAD_STATE', payload: data.state }) } } catch (e) { setError(e instanceof Error ? e.message : 'Unknown error') throw e } finally { setIsLoading(false) } }, [tenantId]) const resetState = useCallback(() => { dispatch({ type: 'RESET', payload: { tenantId } }) }, [tenantId]) // Status helpers const isStepCompleted = useCallback( (stepId: TOMGeneratorStepId) => { const step = state.steps.find((s) => s.id === stepId) return step?.completed ?? false }, [state.steps] ) const getCompletionPercentage = useCallback(() => { const completedSteps = state.steps.filter((s) => s.completed).length return Math.round((completedSteps / totalSteps) * 100) }, [state.steps, totalSteps]) const contextValue: TOMGeneratorContextValue = { state, dispatch, currentStepIndex, totalSteps, canGoNext, canGoPrevious, goToStep, goToNextStep, goToPreviousStep, completeCurrentStep, setCompanyProfile, updateCompanyProfile, setDataProfile, updateDataProfile, setArchitectureProfile, updateArchitectureProfile, setSecurityProfile, updateSecurityProfile, setRiskProfile, updateRiskProfile, addEvidence, updateEvidence, deleteEvidence, deriveTOMs, updateDerivedTOM, bulkUpdateTOMs, runGapAnalysis, addExport, saveState, loadState, resetState, isStepCompleted, getCompletionPercentage, isLoading, error, } return ( {children} ) }