"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.XmppProvider = void 0;
exports.emitXmppLogout = emitXmppLogout;
exports.useXmppClient = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _xmppClient = _interopRequireDefault(require("../networking/xmppClient"));
var _clientRegistry = require("../utils/clientRegistry");
var _resolveInitBeforeLoadUser = require("../helpers/resolveInitBeforeLoadUser");
var _ensureScopedChatCache = require("../helpers/ensureScopedChatCache");
var _rooms = require("../networking/api-requests/rooms.api");
var _allRoomPresences = require("../networking/xmpp/allRoomPresences.xmpp");
var _roomStore = require("../roomStore");
var _chatSettingsSlice = require("../roomStore/chatSettingsSlice");
var _roomsSlice = require("../roomStore/roomsSlice");
var _historyPreloadScheduler = require("../helpers/historyPreloadScheduler");
var _useLocalStorage = require("../hooks/useLocalStorage");
var _LOCAL_STORAGE = require("../helpers/constants/LOCAL_STORAGE");
var _persistence = require("../roomStore/persistence");
var _devLogger = require("../utils/devLogger");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const XmppContext = /*#__PURE__*/(0, _react.createContext)(null);
const LOGOUT_EVENT = 'ethora-xmpp-logout';
const XmppProvider = ({
  children,
  config
}) => {
  const [client, setClient] = (0, _react.useState)(null);
  const [providerBootstrapStatus, setProviderBootstrapStatus] = (0, _react.useState)('idle');
  const [reconnectAttempts, setReconnectAttempts] = (0, _react.useState)(0);

  // Track which "init mode" this provider runs in. When config.initBeforeLoad
  // is true, the provider owns bootstrap and ChatWrapper just waits.
  const initMode = config?.initBeforeLoad ? 'provider' : 'chat';
  const lastCredsRef = (0, _react.useRef)(null);
  const completedBootstrapKeyRef = (0, _react.useRef)('');
  const inflightBootstrapKeyRef = (0, _react.useRef)('');
  const initializeClient = (0, _react.useCallback)(async (username, password, xmppSettings) => {
    const settings = xmppSettings || config?.xmppSettings;
    const key = (0, _clientRegistry.buildXmppClientKey)(username, settings);

    // 1. Reuse global singleton if it's the same key.
    const reusable = (0, _clientRegistry.getReusableXmppClientByKey)(key);
    if (reusable) {
      setClient(reusable);
      lastCredsRef.current = {
        username,
        password,
        settings
      };
      return reusable;
    }

    // 2. Reuse current provider client if still usable.
    if (client && (0, _clientRegistry.isXmppClientReusable)(client)) {
      lastCredsRef.current = {
        username,
        password,
        settings
      };
      return client;
    }

    // 3. Otherwise create a new client under the init lock to dedup
    //    concurrent calls for the same key.
    const created = await (0, _clientRegistry.withXmppClientInitLock)(key, async () => {
      const newClient = new _xmppClient.default(username, password, settings);
      (0, _clientRegistry.setGlobalXmppClient)(newClient, key);
      await newClient.waitForOnline().catch(err => {
        console.error('Error waiting for xmpp online:', err);
        throw err;
      });
      return newClient;
    });
    setClient(created);
    lastCredsRef.current = {
      username,
      password,
      settings
    };
    setReconnectAttempts(0);
    return created;
  }, [client, config?.xmppSettings]);

  // -----------------------------------------------------------
  // initBeforeLoad — eager auth + xmpp connect at provider mount
  // -----------------------------------------------------------
  (0, _react.useEffect)(() => {
    if (!config?.initBeforeLoad) {
      setProviderBootstrapStatus('idle');
      return;
    }
    const abortController = new AbortController();
    let cancelled = false;
    const run = async () => {
      // Stable bootstrap key — re-run only when these change.
      const explicitUser = config?.userLogin?.enabled ? config?.userLogin?.user : null;
      const key = [config?.appId || '', config?.baseUrl || '', explicitUser?.defaultWallet?.walletAddress || '', explicitUser?.xmppUsername || '', explicitUser?.token ? '1' : '0', config?.jwtLogin?.enabled ? config?.jwtLogin?.token || '' : '', config?.xmppSettings?.devServer || ''].join('|');
      if (completedBootstrapKeyRef.current === key) {
        (0, _devLogger.pushLog)('rn', 'initBeforeLoad: already completed (same key) — skip');
        return;
      }
      if (inflightBootstrapKeyRef.current === key) {
        (0, _devLogger.pushLog)('rn', 'initBeforeLoad: in-flight (same key) — skip');
        return;
      }
      inflightBootstrapKeyRef.current = key;
      setProviderBootstrapStatus('running');
      (0, _devLogger.pushLog)('rn', 'initBeforeLoad: running', {
        appId: config?.appId,
        baseUrl: config?.baseUrl,
        mode: config?.jwtLogin?.enabled ? 'jwt' : config?.userLogin?.enabled ? 'userLogin' : config?.customLogin?.enabled ? 'customLogin' : 'fallback'
      });
      try {
        if (config?.clearStoreBeforeInit) {
          (0, _devLogger.pushLog)('rn', 'initBeforeLoad: clearStoreBeforeInit');
          _roomStore.store.dispatch((0, _roomsSlice.setLogoutState)());
          _roomStore.store.dispatch((0, _chatSettingsSlice.logout)());
          await (0, _useLocalStorage.asyncLocalStorage)(_LOCAL_STORAGE.localStorageConstants.ETHORA_USER).remove().catch(() => undefined);
        }
        await (0, _ensureScopedChatCache.ensureScopedChatCache)(config);
        (0, _devLogger.pushLog)('rn', 'initBeforeLoad: scope ok, resolving user…');
        const resolved = await (0, _resolveInitBeforeLoadUser.resolveInitBeforeLoadUser)({
          config,
          signal: abortController.signal
        });
        if (cancelled) {
          return;
        }
        if (!resolved || !resolved.xmppPassword || !resolved.xmppUsername) {
          (0, _devLogger.pushLog)('error', 'initBeforeLoad: user resolve failed', {
            hasResolved: !!resolved,
            xmppUsername: resolved?.xmppUsername,
            xmppPasswordPresent: !!resolved?.xmppPassword
          });
          setProviderBootstrapStatus('failed');
          inflightBootstrapKeyRef.current = '';
          return;
        }
        (0, _devLogger.pushLog)('rn', `initBeforeLoad: user resolved (${resolved.xmppUsername})`);
        (0, _resolveInitBeforeLoadUser.applyResolvedUserToStore)(resolved);

        // Kick off REST /chats/my; allRoomPresences below needs the
        // room list to be in redux, otherwise it joins 0 MUCs and the
        // user receives zero realtime messages until they manually tap
        // into each room. Track the promise so we can await it before
        // joining presences.
        const restRoomsPromise = (0, _rooms.getRooms)().catch(() => undefined);
        const c = await initializeClient(resolved.xmppUsername, resolved.xmppPassword, config?.xmppSettings);
        if (cancelled) {
          return;
        }
        (0, _devLogger.pushLog)('rn', 'initBeforeLoad: xmpp client created, waiting online…');
        await c.waitForOnline();
        if (cancelled) {
          return;
        }
        (0, _devLogger.pushLog)('xmpp', 'initBeforeLoad: xmpp online');
        try {
          await c.getRoomsStanza();
          (0, _devLogger.pushLog)('xmpp', 'initBeforeLoad: getRoomsStanza ok');
        } catch (e) {
          (0, _devLogger.pushLog)('warn', 'initBeforeLoad: getRoomsStanza failed', e);
        }
        try {
          await c.getChatsPrivateStoreRequestStanza();
          (0, _devLogger.pushLog)('xmpp', 'initBeforeLoad: privateStore ok');
        } catch (e) {
          (0, _devLogger.pushLog)('warn', 'initBeforeLoad: privateStore failed', e);
        }
        // Wait for REST /chats/my to populate the room list before
        // joining MUC presences. Race-skipping this step left the user
        // subscribed to 0 rooms and no realtime delivery.
        try {
          await restRoomsPromise;
        } catch {}
        try {
          const roomCount = Object.keys(_roomStore.store.getState().rooms.rooms || {}).length;
          (0, _devLogger.pushLog)('xmpp', `initBeforeLoad: joining ${roomCount} rooms via presence…`);
          await (0, _allRoomPresences.allRoomPresences)(c.client);
          (0, _devLogger.pushLog)('xmpp', 'initBeforeLoad: allRoomPresences ok');
        } catch (e) {
          (0, _devLogger.pushLog)('warn', 'initBeforeLoad: allRoomPresences failed', e);
        }
        _roomStore.store.dispatch((0, _chatSettingsSlice.setStoreClient)(c));
        completedBootstrapKeyRef.current = key;
        inflightBootstrapKeyRef.current = '';
        setProviderBootstrapStatus('ready');
        (0, _devLogger.pushLog)('rn', 'initBeforeLoad: READY');

        // Background-prefetch history for every room (fire-and-forget).
        const qos = config?.historyQoS;
        const activeRoomJID = _roomStore.store.getState().rooms.activeRoomJID || null;
        const defaultRoomJids = config?.defaultRooms?.map(r => typeof r === 'string' ? r : r?.jid).filter(Boolean) || [];
        (0, _historyPreloadScheduler.runHistoryPreloadScheduler)({
          client: c,
          signal: abortController.signal,
          selectedRoomJid: activeRoomJID,
          defaultRoomJids,
          concurrency: qos?.stagedPreloadConcurrency,
          pageSize: qos?.stagedPreloadFirstPassSize,
          roomLimit: qos?.preloadTopKRooms
        }).catch(err => console.warn('History preload scheduler failed', err));
      } catch (error) {
        if (cancelled) {
          return;
        }
        (0, _devLogger.pushLog)('error', 'initBeforeLoad: bootstrap threw', {
          message: error?.message,
          status: error?.response?.status,
          data: error?.response?.data
        });
        inflightBootstrapKeyRef.current = '';
        setProviderBootstrapStatus('failed');
      }
    };
    run();
    return () => {
      cancelled = true;
      abortController.abort();
      // Re-running this effect (deps changed or unmount) must release the
      // in-flight bootstrap key so the next render isn't permanently
      // short-circuited by the early `inflightBootstrapKeyRef === key`
      // check. Without this, a `setClient` mid-bootstrap that re-renders
      // the provider strands the bootstrap in `running`.
      inflightBootstrapKeyRef.current = '';
    };
    // NOTE: `initializeClient` is intentionally NOT in the dep array.
    // It's a `useCallback` that depends on `client` state; including it
    // would force the effect to re-run every time `setClient` fires
    // (which happens DURING bootstrap), causing a cancel/re-init storm.
    // The function is still reachable through closure; its identity drift
    // is irrelevant to bootstrap semantics.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [config?.initBeforeLoad, config?.appId, config?.baseUrl, config?.userLogin?.enabled, config?.userLogin?.user?.defaultWallet?.walletAddress, config?.userLogin?.user?.xmppUsername, config?.userLogin?.user?.token, config?.jwtLogin?.enabled, config?.jwtLogin?.token, config?.xmppSettings?.devServer]);

  // -----------------------------------------------------------
  // Reconnect / reinit on disconnect
  // -----------------------------------------------------------
  (0, _react.useEffect)(() => {
    if (!client) {
      return;
    }
    if (client.status !== 'offline') {
      return;
    }
    if (reconnectAttempts < 3) {
      console.log(`xmpp reconnect attempt ${reconnectAttempts + 1}`);
      client.scheduleReconnect();
      setReconnectAttempts(prev => prev + 1);
      return;
    }

    // After 3 failed reconnects, drop the client and re-init from scratch.
    if (lastCredsRef.current) {
      const {
        username,
        password,
        settings
      } = lastCredsRef.current;
      (0, _clientRegistry.setGlobalXmppClient)(null);
      setClient(null);
      initializeClient(username, password, settings).catch(err => console.error('Full reinit failed:', err));
    }
  }, [client, client?.status, reconnectAttempts, initializeClient]);

  // -----------------------------------------------------------
  // AppState → background: flush lastViewedTimestamp into the
  // server's private store so the next session shows the correct
  // unread state. Without this, leaving the app open in chat and
  // killing it (or just switching apps) never persists "I read up to
  // here" — the next launch reads the stale marker and over-counts
  // unread.
  //
  // Cheap to run on every transition out of `active`: a no-op when
  // there is no client, when nothing has moved past the existing
  // entry, or when this build has `disableLastRead`.
  // -----------------------------------------------------------
  (0, _react.useEffect)(() => {
    const sub = _reactNative.AppState.addEventListener('change', next => {
      if (next === 'active') {
        return;
      }
      const c = client;
      if (!c) {
        return;
      }
      const state = _roomStore.store.getState();
      const rooms = state.rooms?.rooms;
      const activeRoomJID = state.rooms?.activeRoomJID || null;
      // Fire-and-forget — we're going to the background and don't
      // care about the resolution path.
      c.flushLastViewedToPrivateStoreStanza(rooms, {
        activeRoomJID
      }).catch(() => {});
    });
    return () => sub.remove();
  }, [client]);

  // -----------------------------------------------------------
  // Retry-bootstrap listener — fired by ChatWrapper's error modal so
  // a transient failure (bad endpoint, server hiccup, etc.) can be
  // recovered without forcing the user to unmount the whole chat.
  // -----------------------------------------------------------
  (0, _react.useEffect)(() => {
    const sub = _reactNative.DeviceEventEmitter.addListener('ethora:retryBootstrap', () => {
      completedBootstrapKeyRef.current = '';
      inflightBootstrapKeyRef.current = '';
      setProviderBootstrapStatus('idle');
    });
    return () => sub.remove();
  }, []);

  // -----------------------------------------------------------
  // Logout event listener (RN equivalent of window event)
  // -----------------------------------------------------------
  (0, _react.useEffect)(() => {
    const sub = _reactNative.DeviceEventEmitter.addListener(LOGOUT_EVENT, async () => {
      try {
        if (client) {
          await client.disconnect({
            suppressReconnect: true
          });
        }
      } catch (e) {
        console.warn('Logout disconnect failed', e);
      }
      (0, _clientRegistry.setGlobalXmppClient)(null);
      setClient(null);
      completedBootstrapKeyRef.current = '';
      inflightBootstrapKeyRef.current = '';
      setReconnectAttempts(0);
      setProviderBootstrapStatus('idle');
      await (0, _persistence.clearPersistedState)();
    });
    return () => sub.remove();
  }, [client]);
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(XmppContext.Provider, {
    value: {
      client,
      providerBootstrapStatus,
      initMode,
      initializeClient,
      setClient
    },
    children: children
  });
};
exports.XmppProvider = XmppProvider;
const useXmppClient = () => {
  const context = (0, _react.useContext)(XmppContext);
  if (!context) {
    throw new Error('useXmppClient must be used within an XmppProvider');
  }
  return context;
};

/** Trigger a global xmpp logout — mirrors the web `ethora-xmpp-logout` event. */
exports.useXmppClient = useXmppClient;
function emitXmppLogout() {
  _reactNative.DeviceEventEmitter.emit(LOGOUT_EVENT);
}
//# sourceMappingURL=xmppProvider.js.map