"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.useOxy = exports.default = exports.OxyProvider = exports.OxyContextProvider = void 0;
var _react = require("react");
var _reactNative = require("react-native");
var _core = require("../../core");
var _sonner = require("../../lib/sonner");
var _authStore = require("../stores/authStore");
var _shallow = require("zustand/react/shallow");
var _useSessionSocket = require("../hooks/useSessionSocket");
var _useStorage = require("../hooks/useStorage");
var _useLanguageManagement = require("../hooks/useLanguageManagement");
var _useSessionManagement = require("../hooks/useSessionManagement");
var _useAuthOperations = require("./hooks/useAuthOperations");
var _useDeviceManagement = require("../hooks/useDeviceManagement");
var _storageHelpers = require("../utils/storageHelpers");
var _errorHandlers = require("../utils/errorHandlers");
var _bottomSheetManager = require("../navigation/bottomSheetManager");
var _reactQuery = require("@tanstack/react-query");
var _queryClient = require("../hooks/queryClient");
var _keyManager = require("../../crypto/keyManager");
var _i18n = require("../../i18n");
var _avatarUtils = require("../utils/avatarUtils");
var _accountStore = require("../stores/accountStore");
var _jsxRuntime = require("react/jsx-runtime");
const OxyContext = /*#__PURE__*/(0, _react.createContext)(null);
let cachedUseFollowHook = null;
const loadUseFollowHook = () => {
  if (cachedUseFollowHook) {
    return cachedUseFollowHook;
  }
  try {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const {
      useFollow
    } = require('../hooks/useFollow');
    cachedUseFollowHook = useFollow;
    return cachedUseFollowHook;
  } catch (error) {
    if (__DEV__) {
      console.warn('useFollow hook is not available. Please import useFollow from @oxyhq/services directly.', error);
    }
    const fallback = () => {
      throw new Error('useFollow hook is only available in the UI bundle. Import it from @oxyhq/services.');
    };
    cachedUseFollowHook = fallback;
    return cachedUseFollowHook;
  }
};
const OxyProvider = ({
  children,
  oxyServices: providedOxyServices,
  baseURL,
  storageKeyPrefix = 'oxy_session',
  onAuthStateChange,
  onError
}) => {
  const oxyServicesRef = (0, _react.useRef)(null);
  if (!oxyServicesRef.current) {
    if (providedOxyServices) {
      oxyServicesRef.current = providedOxyServices;
    } else if (baseURL) {
      oxyServicesRef.current = new _core.OxyServices({
        baseURL
      });
    } else {
      throw new Error('Either oxyServices or baseURL must be provided to OxyContextProvider');
    }
  }
  const oxyServices = oxyServicesRef.current;
  const {
    user,
    isAuthenticated,
    isLoading,
    error,
    loginSuccess,
    loginFailure,
    logoutStore,
    // Identity sync state and actions
    isIdentitySyncedStore,
    isSyncing,
    setIdentitySynced,
    setSyncing
  } = (0, _authStore.useAuthStore)((0, _shallow.useShallow)(state => ({
    user: state.user,
    isAuthenticated: state.isAuthenticated,
    isLoading: state.isLoading,
    error: state.error,
    loginSuccess: state.loginSuccess,
    loginFailure: state.loginFailure,
    logoutStore: state.logout,
    // Identity sync state and actions
    isIdentitySyncedStore: state.isIdentitySynced,
    isSyncing: state.isSyncing,
    setIdentitySynced: state.setIdentitySynced,
    setSyncing: state.setSyncing
  })));
  const [tokenReady, setTokenReady] = (0, _react.useState)(true);
  const initializedRef = (0, _react.useRef)(false);
  const setAuthState = _authStore.useAuthStore.setState;
  const logger = (0, _react.useCallback)((message, err) => {
    if (__DEV__) {
      console.warn(`[OxyContext] ${message}`, err);
    }
  }, []);
  const storageKeys = (0, _react.useMemo)(() => (0, _storageHelpers.getStorageKeys)(storageKeyPrefix), [storageKeyPrefix]);
  const {
    storage,
    isReady: isStorageReady
  } = (0, _useStorage.useStorage)({
    onError,
    logger
  });

  // Identity integrity check and auto-restore on startup
  // Skip on web platform - identity storage is only available on native platforms
  (0, _react.useEffect)(() => {
    if (!storage || !isStorageReady) return;
    if (_reactNative.Platform.OS === 'web') return; // Identity operations are native-only

    const checkAndRestoreIdentity = async () => {
      try {
        // Check if identity exists and verify integrity
        const hasIdentity = await _keyManager.KeyManager.hasIdentity();
        if (hasIdentity) {
          const isValid = await _keyManager.KeyManager.verifyIdentityIntegrity();
          if (!isValid) {
            // Try to restore from backup
            const restored = await _keyManager.KeyManager.restoreIdentityFromBackup();
            if (__DEV__) {
              logger(restored ? 'Identity restored from backup successfully' : 'Identity integrity check failed - user may need to restore from recovery phrase');
            }
          } else {
            // Identity is valid - ensure backup is up to date
            await _keyManager.KeyManager.backupIdentity();
          }
        } else {
          // No identity - try to restore from backup
          const restored = await _keyManager.KeyManager.restoreIdentityFromBackup();
          if (restored && __DEV__) {
            logger('Identity restored from backup on startup');
          }
        }
      } catch (error) {
        if (__DEV__) {
          logger('Error during identity integrity check', error);
        }
        // Don't block app startup - user can recover with recovery phrase
      }
    };
    checkAndRestoreIdentity();
  }, [storage, isStorageReady, logger]);

  // Offline queuing is now handled by TanStack Query mutations
  // No need for custom offline queue

  const {
    currentLanguage,
    metadata: currentLanguageMetadata,
    languageName: currentLanguageName,
    nativeLanguageName: currentNativeLanguageName,
    setLanguage,
    applyLanguagePreference
  } = (0, _useLanguageManagement.useLanguageManagement)({
    storage,
    languageKey: storageKeys.language,
    onError,
    logger
  });
  const queryClient = (0, _reactQuery.useQueryClient)();
  const {
    sessions,
    activeSessionId,
    setActiveSessionId,
    updateSessions,
    switchSession,
    refreshSessions,
    clearSessionState,
    saveActiveSessionId,
    trackRemovedSession
  } = (0, _useSessionManagement.useSessionManagement)({
    oxyServices,
    storage,
    storageKeyPrefix,
    loginSuccess,
    logoutStore,
    applyLanguagePreference,
    onAuthStateChange,
    onError,
    setAuthError: message => setAuthState({
      error: message
    }),
    logger,
    setTokenReady,
    queryClient
  });
  const {
    createIdentity,
    importIdentity,
    signIn,
    logout,
    logoutAll,
    hasIdentity,
    getPublicKey,
    isIdentitySynced,
    syncIdentity: syncIdentityBase
  } = (0, _useAuthOperations.useAuthOperations)({
    oxyServices,
    storage,
    sessions,
    activeSessionId,
    setActiveSessionId,
    updateSessions,
    saveActiveSessionId,
    clearSessionState,
    switchSession,
    applyLanguagePreference,
    onAuthStateChange,
    onError,
    loginSuccess,
    loginFailure,
    logoutStore,
    setAuthState,
    setIdentitySynced,
    setSyncing,
    logger
  });

  // syncIdentity - TanStack Query handles offline mutations automatically
  const syncIdentity = (0, _react.useCallback)(() => syncIdentityBase(), [syncIdentityBase]);

  // Clear all account data when identity is lost (for accounts app)
  // In accounts app, identity = account, so losing identity means losing everything
  const clearAllAccountData = (0, _react.useCallback)(async () => {
    // Clear TanStack Query cache (in-memory)
    queryClient.clear();

    // Clear persisted query cache
    if (storage) {
      try {
        await (0, _queryClient.clearQueryCache)(storage);
      } catch (error) {
        logger('Failed to clear persisted query cache', error);
      }
    }

    // Clear session state (sessions, activeSessionId, storage)
    await clearSessionState();

    // Clear identity sync state from storage
    if (storage) {
      try {
        await storage.removeItem('oxy_identity_synced');
      } catch (error) {
        logger('Failed to clear identity sync state', error);
      }
    }

    // Reset auth store identity sync state
    _authStore.useAuthStore.getState().setIdentitySynced(false);
    _authStore.useAuthStore.getState().setSyncing(false);

    // Reset account store
    _accountStore.useAccountStore.getState().reset();

    // Clear HTTP service cache
    oxyServices.clearCache();
  }, [queryClient, storage, clearSessionState, logger, oxyServices]);

  // Delete identity and clear all account data
  // In accounts app, deleting identity means losing the account completely
  const deleteIdentityAndClearAccount = (0, _react.useCallback)(async (skipBackup = false, force = false, userConfirmed = false) => {
    // First, clear all account data
    await clearAllAccountData();

    // Then delete the identity keys
    await _keyManager.KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
  }, [clearAllAccountData]);

  // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
  // We only need to sync identity if it's not synced
  (0, _react.useEffect)(() => {
    if (!storage) return;
    let wasOffline = false;
    let checkTimeout = null;

    // Circuit breaker and exponential backoff state
    const stateRef = {
      consecutiveFailures: 0,
      currentInterval: 10000,
      // Start with 10 seconds
      baseInterval: 10000,
      // Base interval in milliseconds
      maxInterval: 60000,
      // Maximum interval (60 seconds)
      maxFailures: 5 // Circuit breaker threshold
    };
    const scheduleNextCheck = () => {
      if (checkTimeout) {
        clearTimeout(checkTimeout);
      }
      checkTimeout = setTimeout(() => {
        checkNetworkAndSync();
      }, stateRef.currentInterval);
    };
    const checkNetworkAndSync = async () => {
      try {
        // Try a lightweight health check to see if we're online
        await oxyServices.healthCheck().catch(() => {
          wasOffline = true;
          throw new Error('Health check failed');
        });

        // Health check succeeded - reset circuit breaker and backoff
        if (stateRef.consecutiveFailures > 0) {
          stateRef.consecutiveFailures = 0;
          stateRef.currentInterval = stateRef.baseInterval;
        }

        // If we were offline and now we're online, sync identity if needed
        if (wasOffline) {
          logger('Network reconnected, checking identity sync...');

          // Sync identity first (if not synced)
          try {
            const hasIdentityValue = await hasIdentity();
            if (hasIdentityValue) {
              // Check sync status directly - sync if not explicitly 'true'
              // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
              const syncStatus = await storage.getItem('oxy_identity_synced');
              if (syncStatus !== 'true') {
                await syncIdentity();
              }
            }
          } catch (syncError) {
            logger('Error syncing identity on reconnect', syncError);
          }

          // TanStack Query will automatically retry pending mutations
          wasOffline = false;
        }
      } catch (error) {
        // Network check failed - we're offline
        wasOffline = true;

        // Increment failure count and apply exponential backoff
        stateRef.consecutiveFailures++;

        // Calculate new interval with exponential backoff, capped at maxInterval
        const backoffMultiplier = Math.min(Math.pow(2, stateRef.consecutiveFailures - 1), stateRef.maxInterval / stateRef.baseInterval);
        stateRef.currentInterval = Math.min(stateRef.baseInterval * backoffMultiplier, stateRef.maxInterval);

        // If we hit the circuit breaker threshold, use max interval
        if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
          stateRef.currentInterval = stateRef.maxInterval;
        }
      } finally {
        // Always schedule next check (will use updated interval)
        scheduleNextCheck();
      }
    };

    // Check immediately
    checkNetworkAndSync();
    return () => {
      if (checkTimeout) {
        clearTimeout(checkTimeout);
      }
    };
  }, [oxyServices, storage, syncIdentity, logger]);
  const {
    getDeviceSessions,
    logoutAllDeviceSessions,
    updateDeviceName
  } = (0, _useDeviceManagement.useDeviceManagement)({
    oxyServices,
    activeSessionId,
    onError,
    clearSessionState,
    logger
  });
  const useFollowHook = loadUseFollowHook();
  const restoreSessionsFromStorage = (0, _react.useCallback)(async () => {
    if (!storage) {
      return;
    }
    setTokenReady(false);
    try {
      const storedSessionIdsJson = await storage.getItem(storageKeys.sessionIds);
      const storedSessionIds = storedSessionIdsJson ? JSON.parse(storedSessionIdsJson) : [];
      const storedActiveSessionId = await storage.getItem(storageKeys.activeSessionId);
      const validSessions = [];
      if (storedSessionIds.length > 0) {
        for (const sessionId of storedSessionIds) {
          try {
            const validation = await oxyServices.validateSession(sessionId, {
              useHeaderValidation: true
            });
            if (validation?.valid && validation.user) {
              const now = new Date();
              validSessions.push({
                sessionId,
                deviceId: '',
                expiresAt: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
                lastActive: now.toISOString(),
                userId: validation.user.id?.toString() ?? '',
                isCurrent: sessionId === storedActiveSessionId
              });
            }
          } catch (validationError) {
            // Silently handle expected 401 errors (expired/invalid sessions) during restoration
            // Only log unexpected errors
            if (!(0, _errorHandlers.isInvalidSessionError)(validationError)) {
              logger('Session validation failed during init', validationError);
            }
          }
        }
        if (validSessions.length > 0) {
          updateSessions(validSessions, {
            merge: false
          });
        }
      }
      if (storedActiveSessionId) {
        try {
          await switchSession(storedActiveSessionId);
        } catch (switchError) {
          // Silently handle expected 401 errors (expired/invalid active session)
          if ((0, _errorHandlers.isInvalidSessionError)(switchError)) {
            await storage.removeItem(storageKeys.activeSessionId);
            updateSessions(validSessions.filter(session => session.sessionId !== storedActiveSessionId), {
              merge: false
            });
            // Don't log expected session errors during restoration
          } else {
            // Only log unexpected errors
            logger('Active session validation error', switchError);
          }
        }
      }
    } catch (error) {
      if (__DEV__) {
        console.error('Auth init error', error);
      }
      await clearSessionState();
    } finally {
      setTokenReady(true);
    }
  }, [clearSessionState, logger, oxyServices, storage, storageKeys.activeSessionId, storageKeys.sessionIds, switchSession, updateSessions]);
  (0, _react.useEffect)(() => {
    if (!storage || initializedRef.current) {
      return;
    }
    initializedRef.current = true;
    void restoreSessionsFromStorage();
  }, [restoreSessionsFromStorage, storage]);
  const activeSession = activeSessionId ? sessions.find(session => session.sessionId === activeSessionId) : undefined;
  const currentDeviceId = activeSession?.deviceId ?? null;
  const userId = user?.id;
  const refreshSessionsWithUser = (0, _react.useCallback)(() => refreshSessions(userId), [refreshSessions, userId]);
  const handleSessionRemoved = (0, _react.useCallback)(sessionId => {
    trackRemovedSession(sessionId);
  }, [trackRemovedSession]);
  const handleRemoteSignOut = (0, _react.useCallback)(() => {
    _sonner.toast.info('You have been signed out remotely.');
    logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
  }, [logger, logout]);
  (0, _useSessionSocket.useSessionSocket)({
    userId,
    activeSessionId,
    currentDeviceId,
    refreshSessions: refreshSessionsWithUser,
    logout,
    clearSessionState,
    baseURL: oxyServices.getBaseURL(),
    onRemoteSignOut: handleRemoteSignOut,
    onSessionRemoved: handleSessionRemoved
  });
  const switchSessionForContext = (0, _react.useCallback)(async sessionId => {
    await switchSession(sessionId);
  }, [switchSession]);

  // Create showBottomSheet function that uses the global function
  const showBottomSheetForContext = (0, _react.useCallback)(screenOrConfig => {
    (0, _bottomSheetManager.showBottomSheet)(screenOrConfig);
  }, []);

  // Create openAvatarPicker function
  const openAvatarPicker = (0, _react.useCallback)(() => {
    showBottomSheetForContext({
      screen: 'FileManagement',
      props: {
        selectMode: true,
        multiSelect: false,
        disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
        afterSelect: 'none',
        // Don't navigate away - stay on current screen
        onSelect: async file => {
          if (!file.contentType.startsWith('image/')) {
            _sonner.toast.error((0, _i18n.translate)(currentLanguage, 'editProfile.toasts.selectImage') || 'Please select an image file');
            return;
          }
          try {
            // Update file visibility to public for avatar
            await (0, _avatarUtils.updateAvatarVisibility)(file.id, oxyServices, 'OxyContext');

            // Update user profile (handles query invalidation and accountStore update)
            await (0, _avatarUtils.updateProfileWithAvatar)({
              avatar: file.id
            }, oxyServices, activeSessionId, queryClient, syncIdentity);
            _sonner.toast.success((0, _i18n.translate)(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
          } catch (e) {
            _sonner.toast.error(e.message || (0, _i18n.translate)(currentLanguage, 'editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
          }
        }
      }
    });
  }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient, syncIdentity]);
  const contextValue = (0, _react.useMemo)(() => ({
    user,
    sessions,
    activeSessionId,
    isAuthenticated,
    isLoading,
    isTokenReady: tokenReady,
    isStorageReady,
    error,
    currentLanguage,
    currentLanguageMetadata,
    currentLanguageName,
    currentNativeLanguageName,
    createIdentity,
    importIdentity,
    signIn,
    hasIdentity,
    getPublicKey,
    isIdentitySynced,
    syncIdentity,
    deleteIdentityAndClearAccount,
    identitySyncState: {
      isSynced: isIdentitySyncedStore ?? true,
      isSyncing: isSyncing ?? false
    },
    logout,
    logoutAll,
    switchSession: switchSessionForContext,
    removeSession: logout,
    refreshSessions: refreshSessionsWithUser,
    setLanguage,
    getDeviceSessions,
    logoutAllDeviceSessions,
    updateDeviceName,
    clearSessionState,
    clearAllAccountData,
    oxyServices,
    useFollow: useFollowHook,
    showBottomSheet: showBottomSheetForContext,
    openAvatarPicker
  }), [activeSessionId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount, isIdentitySyncedStore, isSyncing, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, isAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessionsWithUser, sessions, setLanguage, switchSessionForContext, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, useFollowHook, user, showBottomSheetForContext, openAvatarPicker]);
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(OxyContext.Provider, {
    value: contextValue,
    children: children
  });
};
exports.OxyProvider = OxyProvider;
const OxyContextProvider = exports.OxyContextProvider = OxyProvider;
const useOxy = () => {
  const context = (0, _react.useContext)(OxyContext);
  if (!context) {
    throw new Error('useOxy must be used within an OxyContextProvider');
  }
  return context;
};
exports.useOxy = useOxy;
var _default = exports.default = OxyContext;
//# sourceMappingURL=OxyContext.js.map