export type AppPage = 'auth' | 'onboarding' | 'camera' | 'client' | 'activity' | 'settings'; export type ToastType = 'info' | 'success' | 'error'; export type AppToast = { id: string; type: ToastType; message: string; }; export type LinkedCamera = { id: string; cameraDeviceId: string; clientDeviceId: string; cameraName?: string | null; cameraStatus?: string | null; }; export type MotionNotification = { id: string; cameraDeviceId: string; message: string; createdAt: string; isRead: boolean; }; export type ActivityLogItem = { id: string; type: string; message: string; createdAt: string; }; export type RecordingItem = { id: string; status?: string; createdAt: string; cameraDeviceId?: string; durationSeconds?: number | null; streamSessionId?: string; }; export type Device = { id: string; name: string; role: 'camera' | 'client'; status?: string; }; export type Session = { user?: { id?: string; name?: string; email?: string; }; session?: { id?: string; }; }; export type AppState = { page: AppPage; session: Session | null; device: Device | null; deviceToken: string | null; socketConnected: boolean; isMotionActive: boolean; cameraPermissionGranted: boolean; cameraPreviewReady: boolean; cameraStatus: 'idle' | 'recording'; linkedCameras: LinkedCamera[]; recordings: RecordingItem[]; motionNotifications: MotionNotification[]; activeCameraDeviceId: string | null; activeStreamSessionId: string | null; activityLog: ActivityLogItem[]; cameraSessions: Record; connectedStreamSessionIds: string[]; loading: boolean; isRegistering: boolean; authForm: { email: string; password: string; name: string; }; onboardingForm: { name: string; role: 'camera' | 'client'; pushToken: string; }; toasts: AppToast[]; clientStreamMode: 'none' | 'connecting' | 'unavailable' | 'image' | 'video'; clientFallbackFrame: string; clientPlaceholderText: string; lastError: string | null; }; export const createInitialState = (): AppState => ({ page: 'auth', session: null, device: null, deviceToken: null, socketConnected: false, isMotionActive: false, cameraPermissionGranted: false, cameraPreviewReady: false, cameraStatus: 'idle', linkedCameras: [], recordings: [], motionNotifications: [], activeCameraDeviceId: null, activeStreamSessionId: null, activityLog: [], cameraSessions: {}, connectedStreamSessionIds: [], loading: false, isRegistering: false, authForm: { email: '', password: '', name: '', }, onboardingForm: { name: '', role: 'client', pushToken: '', }, toasts: [], clientStreamMode: 'none', clientFallbackFrame: '', clientPlaceholderText: 'Select a camera to view', lastError: null, }); export const unreadNotificationsCount = (state: AppState): number => state.motionNotifications.reduce((count, item) => count + (item.isRead ? 0 : 1), 0);