import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'; import { nlpApi, Resource, AgentState, Position, Notification, LearningData } from '../services/nlpApi'; interface AppContextType { resources: Resource[]; agent: AgentState; learningData: LearningData | null; bookmarks: string[]; isLoading: boolean; setResources: React.Dispatch>; setAgent: React.Dispatch>; setBookmarks: React.Dispatch>; refreshData: () => Promise; toggleBookmark: (resourceId: string) => Promise; updateAgentPosition: (position: Position) => Promise; visitResource: (resourceId: string) => Promise; levelUpMessage: string | null; setLevelUpMessage: React.Dispatch>; notifications: Notification[]; addNotification: (message: string, type?: 'info' | 'success' | 'warning') => Promise; markNotificationsAsRead: () => void; } const AppContext = createContext(undefined); export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [resources, setResources] = useState([]); const [agent, setAgent] = useState({ position: { x: 10, y: 10 }, level: 1, totalReward: 0, visitedResources: [] }); const [learningData, setLearningData] = useState(null); const [bookmarks, setBookmarks] = useState([]); const [isLoading, setIsLoading] = useState(true); const [levelUpMessage, setLevelUpMessage] = useState(null); const [prevLevel, setPrevLevel] = useState(null); const [notifications, setNotifications] = useState([]); const addNotification = useCallback(async (message: string, type: 'info' | 'success' | 'warning' = 'info') => { try { const newNotif = await nlpApi.addNotification('default', message, type); setNotifications(prev => [newNotif, ...prev]); } catch (error) { console.error('Failed to add notification:', error); } }, []); const markNotificationsAsRead = useCallback(async () => { setNotifications(prev => prev.map(n => ({ ...n, read: true }))); try { await nlpApi.markNotificationsRead('default'); } catch (error) { console.error('Failed to mark notifications as read:', error); } }, []); // Trigger Notification useEffect(() => { if (prevLevel !== null && agent.level > prevLevel) { const msg = `Level up! You are now Stage ${agent.level}`; setLevelUpMessage(msg); addNotification(msg, 'success'); setTimeout(() => setLevelUpMessage(null), 5000); // 5 sec toast } setPrevLevel(agent.level); }, [agent.level, prevLevel, addNotification]); const refreshData = useCallback(async () => { setIsLoading(true); try { const [resData, agentData, bookmarkData, lData] = await Promise.all([ nlpApi.getResources(), nlpApi.getAgentState('default'), nlpApi.getBookmarks('default'), nlpApi.getLearningData('default') ]); setResources(resData); setAgent(agentData); setBookmarks(bookmarkData); setLearningData(lData); if (agentData.notifications) { setNotifications(agentData.notifications); } } catch (error) { console.error('Failed to refresh data:', error); } finally { setIsLoading(false); } }, []); useEffect(() => { refreshData(); }, [refreshData]); const toggleBookmark = async (resourceId: string) => { const isBookmarked = bookmarks.includes(resourceId); try { if (isBookmarked) { await nlpApi.removeBookmark('default', resourceId); setBookmarks(prev => prev.filter(id => id !== resourceId)); } else { await nlpApi.addBookmark('default', resourceId); setBookmarks(prev => [...prev, resourceId]); } } catch (error) { console.error('Failed to toggle bookmark:', error); } }; const updateAgentPosition = async (position: Position) => { try { const newState = await nlpApi.moveAgent('default', position); setAgent(newState); } catch (error) { console.error('Failed to update agent position:', error); } }; const visitResource = async (resourceId: string) => { try { const newState = await nlpApi.visitResource('default', resourceId); setAgent(newState); setResources(prev => prev.map(r => r.id === resourceId ? { ...r, visited: true } : r)); } catch (error) { console.error('Failed to visit resource:', error); } }; return ( {children} ); }; export const useAppContext = () => { const context = useContext(AppContext); if (!context) { throw new Error('useAppContext must be used within an AppProvider'); } return context; };