'use client' import React, { createContext, useContext, useReducer, useEffect, useCallback, useMemo, useRef, useState, } from 'react' import { ComplianceClient, sdkReducer, initialState, StateSyncManager, createStateSyncManager, createDSGVOModule, createComplianceModule, createRAGModule, createSecurityModule, type DSGVOModule, type ComplianceModule, type RAGModule, type SecurityModule, } from '@breakpilot/compliance-sdk-core' import type { SDKState, SDKAction, SDKStep, CheckpointStatus, SyncState, UseCaseAssessment, Risk, Control, UserPreferences, } from '@breakpilot/compliance-sdk-types' import { getStepById, getNextStep, getPreviousStep, getCompletionPercentage, getPhaseCompletionPercentage, } from '@breakpilot/compliance-sdk-types' // ============================================================================= // CONTEXT TYPES // ============================================================================= export interface ComplianceContextValue { // State state: SDKState dispatch: React.Dispatch // Client client: ComplianceClient // Modules dsgvo: DSGVOModule compliance: ComplianceModule rag: RAGModule security: SecurityModule // Navigation currentStep: SDKStep | undefined goToStep: (stepId: string) => void goToNextStep: () => void goToPreviousStep: () => void canGoNext: boolean canGoPrevious: boolean // Progress completionPercentage: number phase1Completion: number phase2Completion: number // Checkpoints validateCheckpoint: (checkpointId: string) => Promise overrideCheckpoint: (checkpointId: string, reason: string) => Promise getCheckpointStatus: (checkpointId: string) => CheckpointStatus | undefined // State Updates updateUseCase: (id: string, data: Partial) => void addRisk: (risk: Risk) => void updateControl: (id: string, data: Partial) => void // Persistence saveState: () => Promise loadState: () => Promise resetState: () => void // Sync syncState: SyncState forceSyncToServer: () => Promise isOnline: boolean // Export exportState: (format: 'json' | 'pdf' | 'zip') => Promise // Command Bar isCommandBarOpen: boolean setCommandBarOpen: (open: boolean) => void // Status isInitialized: boolean isLoading: boolean error: Error | null } export const ComplianceContext = createContext(null) // ============================================================================= // PROVIDER PROPS // ============================================================================= export interface ComplianceProviderProps { children: React.ReactNode apiEndpoint: string apiKey?: string tenantId: string userId?: string enableBackendSync?: boolean onNavigate?: (url: string) => void onError?: (error: Error) => void } // ============================================================================= // PROVIDER // ============================================================================= const SDK_STORAGE_KEY = 'breakpilot-compliance-sdk-state' export function ComplianceProvider({ children, apiEndpoint, apiKey, tenantId, userId = 'default', enableBackendSync = true, onNavigate, onError, }: ComplianceProviderProps) { const [state, dispatch] = useReducer(sdkReducer, { ...initialState, tenantId, userId, }) const [isCommandBarOpen, setCommandBarOpen] = useState(false) const [isInitialized, setIsInitialized] = useState(false) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [syncState, setSyncState] = useState({ status: 'idle', lastSyncedAt: null, localVersion: 0, serverVersion: 0, pendingChanges: 0, error: null, }) const [isOnline, setIsOnline] = useState(true) // Refs const clientRef = useRef(null) const syncManagerRef = useRef(null) // Initialize client if (!clientRef.current) { clientRef.current = new ComplianceClient({ apiEndpoint, apiKey, tenantId, onError: err => { setError(err) onError?.(err) }, }) } const client = clientRef.current // Modules const dsgvo = useMemo( () => createDSGVOModule(client, () => state), [client, state] ) const compliance = useMemo( () => createComplianceModule(client, () => state), [client, state] ) const rag = useMemo(() => createRAGModule(client), [client]) const security = useMemo( () => createSecurityModule(client, () => state), [client, state] ) // Initialize sync manager useEffect(() => { if (enableBackendSync && typeof window !== 'undefined') { syncManagerRef.current = createStateSyncManager( client, tenantId, { debounceMs: 2000, maxRetries: 3 }, { onSyncStart: () => { setSyncState(prev => ({ ...prev, status: 'syncing' })) }, onSyncComplete: syncedState => { setSyncState(prev => ({ ...prev, status: 'idle', lastSyncedAt: new Date(), pendingChanges: 0, })) if (new Date(syncedState.lastModified) > new Date(state.lastModified)) { dispatch({ type: 'SET_STATE', payload: syncedState }) } }, onSyncError: err => { setSyncState(prev => ({ ...prev, status: 'error', error: err.message, })) setError(err) }, onConflict: () => { setSyncState(prev => ({ ...prev, status: 'conflict' })) }, onOffline: () => { setIsOnline(false) setSyncState(prev => ({ ...prev, status: 'offline' })) }, onOnline: () => { setIsOnline(true) setSyncState(prev => ({ ...prev, status: 'idle' })) }, } ) } return () => { syncManagerRef.current?.destroy() } }, [enableBackendSync, tenantId, client]) // Load initial state useEffect(() => { const loadInitialState = async () => { setIsLoading(true) try { // Load from localStorage first if (typeof window !== 'undefined') { const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`) if (stored) { const parsed = JSON.parse(stored) if (parsed.lastModified) { parsed.lastModified = new Date(parsed.lastModified) } dispatch({ type: 'SET_STATE', payload: parsed }) } } // Then load from server if enabled if (enableBackendSync && syncManagerRef.current) { const serverState = await syncManagerRef.current.loadFromServer() if (serverState) { dispatch({ type: 'SET_STATE', payload: serverState }) } } } catch (err) { setError(err as Error) onError?.(err as Error) } finally { setIsLoading(false) setIsInitialized(true) } } loadInitialState() }, [tenantId, enableBackendSync]) // Auto-save useEffect(() => { if (!isInitialized || !state.preferences.autoSave) return const saveTimeout = setTimeout(() => { try { if (typeof window !== 'undefined') { localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state)) } if (enableBackendSync && syncManagerRef.current) { syncManagerRef.current.queueSync(state) } } catch (err) { console.error('Failed to save state:', err) } }, 1000) return () => clearTimeout(saveTimeout) }, [state, tenantId, isInitialized, enableBackendSync]) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault() setCommandBarOpen(prev => !prev) } if (e.key === 'Escape' && isCommandBarOpen) { setCommandBarOpen(false) } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [isCommandBarOpen]) // Navigation const currentStep = useMemo(() => getStepById(state.currentStep), [state.currentStep]) const goToStep = useCallback( (stepId: string) => { const step = getStepById(stepId) if (step) { dispatch({ type: 'SET_CURRENT_STEP', payload: stepId }) onNavigate?.(step.url) } }, [onNavigate] ) const goToNextStep = useCallback(() => { const nextStep = getNextStep(state.currentStep) if (nextStep) { goToStep(nextStep.id) } }, [state.currentStep, goToStep]) const goToPreviousStep = useCallback(() => { const prevStep = getPreviousStep(state.currentStep) if (prevStep) { goToStep(prevStep.id) } }, [state.currentStep, goToStep]) const canGoNext = useMemo(() => getNextStep(state.currentStep) !== undefined, [state.currentStep]) const canGoPrevious = useMemo( () => getPreviousStep(state.currentStep) !== undefined, [state.currentStep] ) // Progress const completionPercentage = useMemo(() => getCompletionPercentage(state), [state]) const phase1Completion = useMemo(() => getPhaseCompletionPercentage(state, 1), [state]) const phase2Completion = useMemo(() => getPhaseCompletionPercentage(state, 2), [state]) // Checkpoints const validateCheckpoint = useCallback( async (checkpointId: string): Promise => { if (enableBackendSync) { try { const result = await client.validateCheckpoint(checkpointId, state) const status: CheckpointStatus = { checkpointId: result.checkpointId, passed: result.passed, validatedAt: new Date(result.validatedAt), validatedBy: result.validatedBy, errors: result.errors, warnings: result.warnings, } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } }) return status } catch { // Fall through to local validation } } // Local validation const status: CheckpointStatus = { checkpointId, passed: true, validatedAt: new Date(), validatedBy: 'SYSTEM', errors: [], warnings: [], } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } }) return status }, [state, enableBackendSync, client] ) const overrideCheckpoint = useCallback( async (checkpointId: string, reason: string): Promise => { const existingStatus = state.checkpoints[checkpointId] const overriddenStatus: CheckpointStatus = { ...existingStatus, checkpointId, passed: true, overrideReason: reason, overriddenBy: state.userId, overriddenAt: new Date(), errors: [], warnings: existingStatus?.warnings || [], } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } }) }, [state.checkpoints, state.userId] ) const getCheckpointStatus = useCallback( (checkpointId: string): CheckpointStatus | undefined => { return state.checkpoints[checkpointId] }, [state.checkpoints] ) // State Updates const updateUseCase = useCallback((id: string, data: Partial) => { dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } }) }, []) const addRisk = useCallback((risk: Risk) => { dispatch({ type: 'ADD_RISK', payload: risk }) }, []) const updateControl = useCallback((id: string, data: Partial) => { dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } }) }, []) // Persistence const saveState = useCallback(async (): Promise => { try { if (typeof window !== 'undefined') { localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state)) } if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forceSync(state) } } catch (err) { setError(err as Error) throw err } }, [state, tenantId, enableBackendSync]) const loadState = useCallback(async (): Promise => { setIsLoading(true) try { if (enableBackendSync && syncManagerRef.current) { const serverState = await syncManagerRef.current.loadFromServer() if (serverState) { dispatch({ type: 'SET_STATE', payload: serverState }) return } } if (typeof window !== 'undefined') { const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`) if (stored) { dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) }) } } } catch (err) { setError(err as Error) throw err } finally { setIsLoading(false) } }, [tenantId, enableBackendSync]) const resetState = useCallback(() => { dispatch({ type: 'RESET_STATE' }) if (typeof window !== 'undefined') { localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`) } }, [tenantId]) // Sync const forceSyncToServer = useCallback(async (): Promise => { if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forceSync(state) } }, [state, enableBackendSync]) // Export const exportState = useCallback( async (format: 'json' | 'pdf' | 'zip'): Promise => { if (format === 'json') { return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' }) } return client.exportState(format) }, [state, client] ) const value: ComplianceContextValue = { state, dispatch, client, dsgvo, compliance, rag, security, currentStep, goToStep, goToNextStep, goToPreviousStep, canGoNext, canGoPrevious, completionPercentage, phase1Completion, phase2Completion, validateCheckpoint, overrideCheckpoint, getCheckpointStatus, updateUseCase, addRisk, updateControl, saveState, loadState, resetState, syncState, forceSyncToServer, isOnline, exportState, isCommandBarOpen, setCommandBarOpen, isInitialized, isLoading, error, } return {children} }