import React, { createContext, useContext, useReducer, useCallback } from 'react'; import type { OSState, OSAction, WindowState } from '../types'; import { APPS } from '../config/apps'; import { MENUBAR_HEIGHT, DOCK_HEIGHT } from '../config/layout'; function initWindows(): Record { const map: Record = {}; for (const app of APPS) { map[app.id] = { id: app.id, isOpen: false, isMinimized: false, isMaximized: false, zIndex: 1, position: { ...app.defaultPosition }, size: { ...app.defaultSize }, prevPosition: { ...app.defaultPosition }, prevSize: { ...app.defaultSize }, }; } return map; } const initialState: OSState = { windows: initWindows(), topZ: 10, wallpaper: 0, }; function reducer(state: OSState, action: OSAction): OSState { switch (action.type) { case 'OPEN': { const newZ = state.topZ + 1; return { ...state, topZ: newZ, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], isOpen: true, isMinimized: false, zIndex: newZ, }, }, }; } case 'CLOSE': return { ...state, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], isOpen: false, isMinimized: false, isMaximized: false }, }, }; case 'MINIMIZE': return { ...state, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], isMinimized: true }, }, }; case 'MAXIMIZE': { const win = state.windows[action.id]; if (win.isMaximized) { return { ...state, windows: { ...state.windows, [action.id]: { ...win, isMaximized: false, position: { ...win.prevPosition }, size: { ...win.prevSize }, }, }, }; } return { ...state, windows: { ...state.windows, [action.id]: { ...win, isMaximized: true, prevPosition: { ...win.position }, prevSize: { ...win.size }, position: { x: 0, y: MENUBAR_HEIGHT }, // viewport dims passed in from caller — keeps reducer pure size: { width: action.viewportWidth, height: action.viewportHeight - MENUBAR_HEIGHT - DOCK_HEIGHT }, }, }, }; } case 'FOCUS': { const newZ = state.topZ + 1; return { ...state, topZ: newZ, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], zIndex: newZ, isMinimized: false }, }, }; } case 'MOVE': return { ...state, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], position: { x: action.x, y: action.y } }, }, }; case 'RESIZE': return { ...state, windows: { ...state.windows, [action.id]: { ...state.windows[action.id], size: { width: action.width, height: action.height } }, }, }; case 'SET_WALLPAPER': return { ...state, wallpaper: action.index }; default: return state; } } interface WindowContextType { state: OSState; openWindow: (id: string) => void; closeWindow: (id: string) => void; minimizeWindow: (id: string) => void; maximizeWindow: (id: string, viewportWidth: number, viewportHeight: number) => void; focusWindow: (id: string) => void; moveWindow: (id: string, x: number, y: number) => void; resizeWindow: (id: string, width: number, height: number) => void; setWallpaper: (index: number) => void; } const WindowContext = createContext(null); export function WindowProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(reducer, initialState); const openWindow = useCallback((id: string) => dispatch({ type: 'OPEN', id }), []); const closeWindow = useCallback((id: string) => dispatch({ type: 'CLOSE', id }), []); const minimizeWindow = useCallback((id: string) => dispatch({ type: 'MINIMIZE', id }), []); const maximizeWindow = useCallback((id: string, viewportWidth: number, viewportHeight: number) => dispatch({ type: 'MAXIMIZE', id, viewportWidth, viewportHeight }), []); const focusWindow = useCallback((id: string) => dispatch({ type: 'FOCUS', id }), []); const moveWindow = useCallback((id: string, x: number, y: number) => dispatch({ type: 'MOVE', id, x, y }), []); const resizeWindow = useCallback((id: string, w: number, h: number) => dispatch({ type: 'RESIZE', id, width: w, height: h }), []); const setWallpaper = useCallback((index: number) => dispatch({ type: 'SET_WALLPAPER', index }), []); return ( {children} ); } export function useOS() { const ctx = useContext(WindowContext); if (!ctx) throw new Error('useOS must be used within WindowProvider'); return ctx; }