/* eslint-disable react-hooks/exhaustive-deps */
import AsyncStorage from '@react-native-async-storage/async-storage';
import { io, type Socket } from 'socket.io-client';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Linking } from 'react-native';
import type { CameraView } from 'expo-camera';

import { createApi } from '@/src/api';
import { API_BASE_URL } from '@/src/config';
import {
  createInitialState,
  type AppPage,
  type AppState,
  type LinkedCamera,
  type MotionNotification,
  unreadNotificationsCount,
} from '@/src/state';

const DEVICE_STORAGE_KEY = 'mobileSimDevice';

type AppContextValue = {
  state: AppState;
  ready: boolean;
  unreadCount: number;
  actions: AppActions;
};

type AppActions = {
  setPage: (page: AppPage) => void;
  setAuthField: (field: 'email' | 'password' | 'name', value: string) => void;
  toggleAuthMode: () => void;
  submitAuth: () => Promise<void>;
  setOnboardingField: (field: 'name' | 'pushToken', value: string) => void;
  selectRole: (role: 'camera' | 'client') => void;
  registerDevice: () => Promise<void>;
  loadSavedDevice: () => Promise<void>;
  signOut: () => Promise<void>;
  startMotion: () => Promise<void>;
  endMotion: () => Promise<void>;
  goOnline: () => Promise<void>;
  refreshCameraInputs: () => Promise<void>;
  selectCameraInput: (_cameraInputId: string) => Promise<void>;
  linkCamera: (cameraDeviceId: string) => Promise<void>;
  renameLinkedCamera: (cameraDeviceId: string, nextName: string) => Promise<void>;
  deleteLinkedCamera: (linkId: string) => Promise<void>;
  requestStream: (cameraDeviceId: string) => Promise<void>;
  selectCamera: (cameraDeviceId: string) => Promise<void>;
  closeStreamViewer: () => void;
  openRecording: (recordingId: string) => Promise<void>;
  openMotionNotificationTarget: (notificationId: string, cameraDeviceId: string) => Promise<void>;
  markAllNotificationsRead: () => void;
  clearNotifications: () => void;
  refreshClientData: () => Promise<void>;
  runDiagnostics: () => void;
  removeToast: (id: string) => void;
  setCameraPermissionGranted: (granted: boolean) => void;
  setCameraPreviewReady: (ready: boolean) => void;
  setCameraRef: (ref: CameraView | null) => void;
};

const AppContext = createContext<AppContextValue | undefined>(undefined);

const makeId = (): string => {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }
  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
};

const getCameraLabel = (cameraDeviceId: string, cameraName?: string | null): string => {
  const explicitName = cameraName?.trim();
  if (explicitName) return explicitName;
  return `Camera ${cameraDeviceId.slice(0, 6)}`;
};

export function AppProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState<AppState>(createInitialState());
  const [ready, setReady] = useState(false);

  const stateRef = useRef(state);
  const socketRef = useRef<Socket | null>(null);
  const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const requestedStreamsRef = useRef<Set<string>>(new Set());
  const lastMotionEventIdRef = useRef<string | null>(null);
  const activeRecordingStreamSessionIdRef = useRef<string | null>(null);
  const frameRelayTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const frameRelayBusyRef = useRef(false);
  const cameraRef = useRef<CameraView | null>(null);
  const initDoneRef = useRef(false);

  useEffect(() => {
    stateRef.current = state;
  }, [state]);

  const api = useMemo(() => createApi(() => stateRef.current.deviceToken), []);

  const setAppState = (partial: Partial<AppState>) => {
    setState((prev) => ({ ...prev, ...partial }));
  };

  const patchAppState = (updater: (prev: AppState) => Partial<AppState>) => {
    setState((prev) => ({ ...prev, ...updater(prev) }));
  };

  const setClientStreamMode = (mode: AppState['clientStreamMode']) => {
    let text = 'Select a camera to view';
    if (mode === 'connecting') text = 'Connecting stream...';
    if (mode === 'unavailable') text = 'Stream unavailable';

    patchAppState((prev) => ({
      clientStreamMode: mode,
      clientPlaceholderText: text,
      clientFallbackFrame: mode === 'image' ? prev.clientFallbackFrame : '',
    }));
  };

  const pushToast = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
    const id = makeId();
    patchAppState((prev) => ({
      toasts: [...prev.toasts, { id, message, type }].slice(-6),
    }));

    setTimeout(() => {
      setState((prev) => ({ ...prev, toasts: prev.toasts.filter((toast) => toast.id !== id) }));
    }, 3200);
  };

  const removeToast = (id: string) => {
    patchAppState((prev) => ({
      toasts: prev.toasts.filter((toast) => toast.id !== id),
    }));
  };

  const addActivity = (type: string, message: string) => {
    const item = {
      id: makeId(),
      type,
      message,
      createdAt: new Date().toISOString(),
    };

    patchAppState((prev) => ({
      activityLog: [item, ...prev.activityLog].slice(0, 200),
    }));
  };

  const markMotionNotificationRead = (notificationId: string) => {
    patchAppState((prev) => ({
      motionNotifications: prev.motionNotifications.map((notification) =>
        notification.id === notificationId ? { ...notification, isRead: true } : notification,
      ),
    }));
  };

  const markAllNotificationsRead = () => {
    patchAppState((prev) => ({
      motionNotifications: prev.motionNotifications.map((notification) =>
        notification.isRead ? notification : { ...notification, isRead: true },
      ),
    }));
  };

  const stopPolling = () => {
    if (pollTimerRef.current) {
      clearInterval(pollTimerRef.current);
      pollTimerRef.current = null;
    }
  };

  const clearClientStream = () => {
    setClientStreamMode('none');
  };

  const stopFrameRelay = () => {
    if (frameRelayTimerRef.current) {
      clearInterval(frameRelayTimerRef.current);
      frameRelayTimerRef.current = null;
    }
    frameRelayBusyRef.current = false;
  };

  const captureAndRelayFrame = async (streamSessionId: string, toDeviceId: string) => {
    if (frameRelayBusyRef.current) return;
    if (!socketRef.current || !cameraRef.current) return;

    try {
      frameRelayBusyRef.current = true;
      const photo = await cameraRef.current.takePictureAsync({
        base64: true,
        quality: 0.45,
        skipProcessing: true,
      });

      if (!photo?.base64) return;

      socketRef.current.emit('stream:frame', {
        toDeviceId,
        streamSessionId,
        frame: `data:image/jpeg;base64,${photo.base64}`,
        capturedAt: new Date().toISOString(),
      });
    } catch {
      // ignore transient camera capture errors
    } finally {
      frameRelayBusyRef.current = false;
    }
  };

  const startFrameRelay = (streamSessionId: string, toDeviceId: string) => {
    if (!streamSessionId || !toDeviceId) return;
    stopFrameRelay();

    frameRelayTimerRef.current = setInterval(() => {
      void captureAndRelayFrame(streamSessionId, toDeviceId);
    }, 700);
  };

  const pollClientData = async () => {
    const current = stateRef.current;
    if (!current.device || current.device.role !== 'client') return;

    const [recs, links, deviceList] = await Promise.all([
      api.ops.listRecordings().catch(() => ({ recordings: [] })),
      api.devices.listLinks().catch(() => ({ links: [] })),
      api.devices.list().catch(() => ({ devices: [] })),
    ]);

    const cameraById = new Map(
      (deviceList.devices || [])
        .filter((entry) => entry.role === 'camera')
        .map((entry) => [entry.id, entry]),
    );

    const linkedCameras: LinkedCamera[] = (links.links || []).map((link) => {
      const camera = cameraById.get(link.cameraDeviceId);
      return {
        ...link,
        cameraName: camera?.name ?? null,
        cameraStatus: camera?.status ?? 'offline',
      };
    });

    setAppState({
      recordings: recs.recordings || [],
      linkedCameras,
    });

    for (const link of linkedCameras) {
      if (!requestedStreamsRef.current.has(link.cameraDeviceId)) {
        requestedStreamsRef.current.add(link.cameraDeviceId);
        await api.streams.request(link.cameraDeviceId).catch(() => undefined);
      }
    }
  };

  const startPolling = () => {
    stopPolling();
    void pollClientData();
    pollTimerRef.current = setInterval(() => {
      void pollClientData();
    }, 5000);
  };

  const disconnectSocket = () => {
    stopFrameRelay();
    if (socketRef.current) {
      socketRef.current.disconnect();
      socketRef.current = null;
    }
    setAppState({ socketConnected: false, connectedStreamSessionIds: [] });
  };

  const connectSocket = () => {
    const token = stateRef.current.deviceToken;
    if (!token) return;

    disconnectSocket();

    const socket = io(API_BASE_URL, {
      auth: { token },
      transports: ['websocket'],
    });

    socketRef.current = socket;

    socket.on('connect', () => {
      setAppState({ socketConnected: true });
      addActivity('System', 'Connected to realtime server');
    });

    socket.on('disconnect', () => {
      stopFrameRelay();
      activeRecordingStreamSessionIdRef.current = null;
      setAppState({ socketConnected: false, activeStreamSessionId: null, cameraStatus: 'idle' });
      clearClientStream();
      addActivity('System', 'Realtime disconnected');
    });

    socket.on('command:received', async (payload) => {
      addActivity('Command', `Received ${payload.commandType}`);

      if (payload.commandType !== 'start_stream') {
        socket.emit('command:ack', { commandId: payload.commandId, status: 'acknowledged' });
        return;
      }

      try {
        const streamId = payload?.payload?.streamSessionId;
        const sourceDeviceId = payload?.sourceDeviceId;
        if (streamId) {
          await api.streams.accept(streamId);
          await api.streams.getPublishCreds(streamId).catch(() => undefined);
          activeRecordingStreamSessionIdRef.current = streamId;
          setAppState({ cameraStatus: 'recording' });
          if (sourceDeviceId) {
            startFrameRelay(streamId, sourceDeviceId);
          }
        }
        addActivity('Stream', 'Accepted stream command and started camera frame relay');
        socket.emit('command:ack', { commandId: payload.commandId, status: 'acknowledged' });
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Command rejected';
        socket.emit('command:ack', { commandId: payload.commandId, status: 'rejected', error: message });
      }
    });

    socket.on('motion:detected', (payload) => {
      const cameraDeviceId = payload.cameraDeviceId || payload.deviceId;
      if (!cameraDeviceId) return;

      const notification: MotionNotification = {
        id: makeId(),
        cameraDeviceId,
        message: `${getCameraLabel(cameraDeviceId)} has detected movement`,
        createdAt: new Date().toISOString(),
        isRead: false,
      };

      patchAppState((prev) => ({
        motionNotifications: [notification, ...prev.motionNotifications].slice(0, 50),
        activeCameraDeviceId: cameraDeviceId,
      }));

      void api.streams.request(cameraDeviceId).catch(() => undefined);
      pushToast('Motion detected', 'info');
      addActivity('Motion', notification.message);
    });

    socket.on('stream:started', async (payload) => {
      const current = stateRef.current;
      const cameraSessions = { ...current.cameraSessions, [payload.cameraDeviceId]: payload.streamSessionId };

      setAppState({ cameraSessions });
      addActivity('Stream', 'Stream is live, connecting...');

      if (payload.cameraDeviceId === current.activeCameraDeviceId) {
        setAppState({ activeStreamSessionId: payload.streamSessionId });
        setClientStreamMode('connecting');
      }

      await api.streams.getSubscribeCreds(payload.streamSessionId).catch(() => undefined);
    });

    socket.on('stream:frame', (payload) => {
      if (!payload?.frame || payload.streamSessionId !== stateRef.current.activeStreamSessionId) return;
      patchAppState((prev) => ({
        clientFallbackFrame: payload.frame,
        connectedStreamSessionIds: prev.connectedStreamSessionIds.includes(payload.streamSessionId)
          ? prev.connectedStreamSessionIds
          : [...prev.connectedStreamSessionIds, payload.streamSessionId],
      }));
      setClientStreamMode('image');
    });

    socket.on('stream:ended', (payload) => {
      if (!payload?.streamSessionId) return;
      if (activeRecordingStreamSessionIdRef.current === payload.streamSessionId) {
        stopFrameRelay();
        activeRecordingStreamSessionIdRef.current = null;
        setAppState({ cameraStatus: 'idle' });
      }
      if (payload.streamSessionId === stateRef.current.activeStreamSessionId) {
        setAppState({ activeStreamSessionId: null });
        clearClientStream();
      }
      patchAppState((prev) => ({
        connectedStreamSessionIds: prev.connectedStreamSessionIds.filter((id) => id !== payload.streamSessionId),
      }));
      addActivity('Stream', 'Remote stream ended');
    });

    socket.on('error:webrtc_signal', (payload) => {
      const message = payload?.message || 'WebRTC signaling error';
      addActivity('WebRTC', message);
      pushToast(message, 'error');
    });
  };

  const cleanupConnectionState = async () => {
    stopPolling();
    stopFrameRelay();
    activeRecordingStreamSessionIdRef.current = null;
    disconnectSocket();
    requestedStreamsRef.current.clear();
    setAppState({
      activeCameraDeviceId: null,
      activeStreamSessionId: null,
      cameraSessions: {},
      connectedStreamSessionIds: [],
      cameraStatus: 'idle',
      clientFallbackFrame: '',
      clientStreamMode: 'none',
      clientPlaceholderText: 'Select a camera to view',
    });
  };

  // This initialization intentionally runs once; lifecycle functions use refs for latest state.
  useEffect(() => {
    const init = async () => {
      if (initDoneRef.current) return;
      initDoneRef.current = true;

      try {
        const saved = await AsyncStorage.getItem(DEVICE_STORAGE_KEY);
        if (saved) {
          const parsed = JSON.parse(saved) as { device?: AppState['device']; deviceToken?: string };
          setAppState({
            device: parsed.device ?? null,
            deviceToken: parsed.deviceToken ?? null,
            onboardingForm: {
              ...stateRef.current.onboardingForm,
              name: parsed.device?.name ?? '',
              role: parsed.device?.role ?? 'client',
            },
          });
        }
      } catch {
        // ignore invalid saved payload
      }

      try {
        const session = await api.auth.getSession();
        if ((session as { session?: unknown })?.session) {
          setAppState({ session: session as AppState['session'] });

          if (stateRef.current.deviceToken) {
            connectSocket();
            startPolling();
          }
        } else {
          setAppState({ session: null });
        }
      } catch {
        setAppState({ session: null });
      }

      setReady(true);
    };

    void init();

    return () => {
      void cleanupConnectionState();
    };
  }, []);

  const actions: AppActions = {
    setPage(page) {
      setAppState({ page });
      if (page === 'activity') {
        markAllNotificationsRead();
      }
      if (page === 'client') {
        void pollClientData();
      }
    },

    setAuthField(field, value) {
      patchAppState((prev) => ({
        authForm: { ...prev.authForm, [field]: value },
      }));
    },

    toggleAuthMode() {
      patchAppState((prev) => ({ isRegistering: !prev.isRegistering }));
    },

    async submitAuth() {
      const current = stateRef.current;
      const { email, password, name } = current.authForm;
      const normalizedName = name || email.split('@')[0] || 'User';

      if (!email.trim() || !password.trim()) {
        pushToast('Email and password are required', 'error');
        return;
      }

      try {
        if (current.isRegistering) {
          await api.auth.signUp({ email: email.trim(), password, name: normalizedName });
        }

        await api.auth.signIn({ email: email.trim(), password });
        const session = await api.auth.getSession();
        setAppState({
          session: session as AppState['session'],
          authForm: { ...current.authForm, password: '' },
        });
        pushToast('Signed in successfully', 'success');

        if (stateRef.current.deviceToken) {
          connectSocket();
          startPolling();
        }
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Authentication failed';
        pushToast(message, 'error');
      }
    },

    setOnboardingField(field, value) {
      patchAppState((prev) => ({
        onboardingForm: { ...prev.onboardingForm, [field]: value },
      }));
    },

    selectRole(role) {
      patchAppState((prev) => ({
        onboardingForm: { ...prev.onboardingForm, role },
      }));
    },

    async registerDevice() {
      const { onboardingForm } = stateRef.current;
      const name = onboardingForm.name.trim() || 'Mobile Dashboard';

      try {
        const payload: Record<string, unknown> = {
          name,
          role: onboardingForm.role,
          platform: 'mobile',
          appVersion: 'mobileapp-1.0',
        };

        if (onboardingForm.pushToken.trim()) {
          payload.pushToken = onboardingForm.pushToken.trim();
        }

        const result = await api.devices.register(payload);

        setAppState({
          device: result.device,
          deviceToken: result.deviceToken,
        });

        await AsyncStorage.setItem(
          DEVICE_STORAGE_KEY,
          JSON.stringify({ device: result.device, deviceToken: result.deviceToken }),
        );

        connectSocket();
        startPolling();
        pushToast('Device registered', 'success');
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Device registration failed';
        pushToast(message, 'error');
      }
    },

    async loadSavedDevice() {
      try {
        const saved = await AsyncStorage.getItem(DEVICE_STORAGE_KEY);
        if (!saved) {
          pushToast('No saved device found', 'info');
          return;
        }

        const parsed = JSON.parse(saved) as { device?: AppState['device']; deviceToken?: string };
        setAppState({
          device: parsed.device ?? null,
          deviceToken: parsed.deviceToken ?? null,
          onboardingForm: {
            ...stateRef.current.onboardingForm,
            name: parsed.device?.name ?? '',
            role: parsed.device?.role ?? 'client',
          },
        });

        if (parsed.deviceToken && stateRef.current.session) {
          connectSocket();
          startPolling();
        }

        pushToast('Loaded saved device', 'success');
      } catch {
        pushToast('Saved device is invalid', 'error');
      }
    },

    async signOut() {
      try {
        await api.auth.signOut();
      } catch {
        // ignore
      }

      await cleanupConnectionState();
      await AsyncStorage.removeItem(DEVICE_STORAGE_KEY);
      lastMotionEventIdRef.current = null;

      setState({
        ...createInitialState(),
        toasts: [],
      });

      pushToast('Signed out', 'info');
    },

    async startMotion() {
      if (!stateRef.current.cameraPermissionGranted) {
        pushToast('Camera permission is required', 'error');
        return;
      }
      try {
        const response = await api.events.startMotion();
        lastMotionEventIdRef.current = response.event.id;
        setAppState({ isMotionActive: true, cameraStatus: 'recording' });
        addActivity('Motion', `Started event ${response.event.id}`);
        pushToast('Motion event started', 'success');
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to start motion';
        pushToast(message, 'error');
      }
    },

    async endMotion() {
      const eventId = lastMotionEventIdRef.current;
      if (!eventId) return;

      try {
        const streamSessionId = activeRecordingStreamSessionIdRef.current;
        if (streamSessionId) {
          await api.streams.end(streamSessionId).catch(() => undefined);
          stopFrameRelay();
          activeRecordingStreamSessionIdRef.current = null;
        }
        await api.events.endMotion(eventId);
        lastMotionEventIdRef.current = null;
        setAppState({ isMotionActive: false, cameraStatus: 'idle' });
        addActivity('Motion', 'Ended event');
        pushToast('Motion ended', 'success');
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to end motion';
        pushToast(message, 'error');
      }
    },

    async goOnline() {
      connectSocket();
      pushToast('Attempting realtime reconnect', 'info');
    },

    async refreshCameraInputs() {
      pushToast('Camera input selection is not available in this mobile build', 'info');
    },

    async selectCameraInput(_cameraInputId: string) {
      pushToast('Camera input switching is not available in this mobile build', 'info');
    },

    async linkCamera(cameraDeviceId: string) {
      const clientDeviceId = stateRef.current.device?.id;
      if (!clientDeviceId || !cameraDeviceId.trim()) {
        pushToast('Camera device ID is required', 'error');
        return;
      }

      try {
        await api.devices.link(cameraDeviceId.trim(), clientDeviceId);
        pushToast('Camera linked', 'success');
        await pollClientData();
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to link camera';
        pushToast(message, 'error');
      }
    },

    async renameLinkedCamera(cameraDeviceId: string, nextName: string) {
      const name = nextName.trim();
      if (!name) {
        pushToast('Camera name cannot be empty', 'error');
        return;
      }

      try {
        await api.devices.update(cameraDeviceId, { name });
        patchAppState((prev) => ({
          linkedCameras: prev.linkedCameras.map((entry) =>
            entry.cameraDeviceId === cameraDeviceId ? { ...entry, cameraName: name } : entry,
          ),
        }));
        pushToast('Camera renamed', 'success');
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to rename camera';
        pushToast(message, 'error');
      }
    },

    async deleteLinkedCamera(linkId: string) {
      const link = stateRef.current.linkedCameras.find((entry) => entry.id === linkId);
      if (!link) return;

      try {
        await api.devices.unlink(linkId);
        const remaining = stateRef.current.linkedCameras.filter((entry) => entry.id !== linkId);
        const isDeletedActive = stateRef.current.activeCameraDeviceId === link.cameraDeviceId;

        requestedStreamsRef.current.delete(link.cameraDeviceId);

        setAppState({
          linkedCameras: remaining,
          activeCameraDeviceId: isDeletedActive ? null : stateRef.current.activeCameraDeviceId,
          activeStreamSessionId: isDeletedActive ? null : stateRef.current.activeStreamSessionId,
        });

        if (isDeletedActive) {
          clearClientStream();
        }

        pushToast('Camera link removed', 'success');
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to remove camera link';
        pushToast(message, 'error');
      }
    },

    async requestStream(cameraDeviceId: string) {
      try {
        await api.streams.request(cameraDeviceId);
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to request stream';
        pushToast(message, 'error');
      }
    },

    async selectCamera(cameraDeviceId: string) {
      const sessions = stateRef.current.cameraSessions || {};
      setAppState({
        activeCameraDeviceId: cameraDeviceId,
        activeStreamSessionId: sessions[cameraDeviceId] || null,
      });

      await actions.requestStream(cameraDeviceId);

      if (!stateRef.current.activeStreamSessionId) {
        setClientStreamMode('connecting');
      }
    },

    closeStreamViewer() {
      setAppState({ activeCameraDeviceId: null, activeStreamSessionId: null });
      clearClientStream();
    },

    async openRecording(recordingId: string) {
      try {
        const result = await api.ops.getRecordingDownloadUrl(recordingId);
        if (!result?.downloadUrl) {
          pushToast('Recording URL unavailable', 'error');
          return;
        }
        await Linking.openURL(result.downloadUrl);
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Failed to open recording';
        pushToast(message, 'error');
      }
    },

    async openMotionNotificationTarget(notificationId: string, cameraDeviceId: string) {
      markMotionNotificationRead(notificationId);
      if (!cameraDeviceId) return;

      const recs = await api.ops.listRecordings().catch(() => ({ recordings: [] }));
      const readyRecording = (recs.recordings || [])
        .filter((recording) => recording.cameraDeviceId === cameraDeviceId && recording.status === 'ready')
        .sort(
          (left, right) =>
            new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime(),
        )[0];

      if (readyRecording?.id) {
        await actions.openRecording(readyRecording.id);
        return;
      }

      await actions.requestStream(cameraDeviceId);
    },

    markAllNotificationsRead() {
      markAllNotificationsRead();
    },

    clearNotifications() {
      setAppState({ motionNotifications: [] });
    },

    async refreshClientData() {
      await pollClientData();
    },

    runDiagnostics() {
      const connected = stateRef.current.socketConnected ? 'connected' : 'disconnected';
      pushToast(`Diagnostics complete: realtime ${connected}`, 'success');
    },

    setCameraPermissionGranted(granted: boolean) {
      setAppState({ cameraPermissionGranted: granted });
    },

    setCameraPreviewReady(isReady: boolean) {
      setAppState({ cameraPreviewReady: isReady });
    },

    setCameraRef(ref: CameraView | null) {
      cameraRef.current = ref;
    },

    removeToast,
  };

  const contextValue: AppContextValue = {
    state,
    ready,
    unreadCount: unreadNotificationsCount(state),
    actions,
  };

  return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
}

export function useApp(): AppContextValue {
  const value = useContext(AppContext);
  if (!value) {
    throw new Error('useApp must be used within an AppProvider');
  }
  return value;
}
