"use strict";

import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { AppState, DeviceEventEmitter } from 'react-native';
import XmppClient from '../networking/xmppClient';
import { buildXmppClientKey, getReusableXmppClientByKey, isXmppClientReusable, setGlobalXmppClient, withXmppClientInitLock } from '../utils/clientRegistry';
import { applyResolvedUserToStore, resolveInitBeforeLoadUser } from '../helpers/resolveInitBeforeLoadUser';
import { ensureScopedChatCache } from '../helpers/ensureScopedChatCache';
import { getRooms as prefetchRoomsViaRest } from '../networking/api-requests/rooms.api';
import { allRoomPresences } from '../networking/xmpp/allRoomPresences.xmpp';
import { store } from '../roomStore';
import { logout, setStoreClient } from '../roomStore/chatSettingsSlice';
import { setLogoutState } from '../roomStore/roomsSlice';
import { runHistoryPreloadScheduler } from '../helpers/historyPreloadScheduler';
import { asyncLocalStorage } from '../hooks/useLocalStorage';
import { localStorageConstants } from '../helpers/constants/LOCAL_STORAGE';
import { clearPersistedState } from '../roomStore/persistence';
import { pushLog as devPushLog } from '../utils/devLogger';
import { jsx as _jsx } from "react/jsx-runtime";
const XmppContext = /*#__PURE__*/createContext(null);
const LOGOUT_EVENT = 'ethora-xmpp-logout';
export const XmppProvider = ({
  children,
  config
}) => {
  const [client, setClient] = useState(null);
  const [providerBootstrapStatus, setProviderBootstrapStatus] = useState('idle');
  const [reconnectAttempts, setReconnectAttempts] = 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 = useRef(null);
  const completedBootstrapKeyRef = useRef('');
  const inflightBootstrapKeyRef = useRef('');
  const initializeClient = useCallback(async (username, password, xmppSettings) => {
    const settings = xmppSettings || config?.xmppSettings;
    const key = buildXmppClientKey(username, settings);

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

    // 2. Reuse current provider client if still usable.
    if (client && 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 withXmppClientInitLock(key, async () => {
      const newClient = new XmppClient(username, password, settings);
      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
  // -----------------------------------------------------------
  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) {
        devPushLog('rn', 'initBeforeLoad: already completed (same key) — skip');
        return;
      }
      if (inflightBootstrapKeyRef.current === key) {
        devPushLog('rn', 'initBeforeLoad: in-flight (same key) — skip');
        return;
      }
      inflightBootstrapKeyRef.current = key;
      setProviderBootstrapStatus('running');
      devPushLog('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) {
          devPushLog('rn', 'initBeforeLoad: clearStoreBeforeInit');
          store.dispatch(setLogoutState());
          store.dispatch(logout());
          await asyncLocalStorage(localStorageConstants.ETHORA_USER).remove().catch(() => undefined);
        }
        await ensureScopedChatCache(config);
        devPushLog('rn', 'initBeforeLoad: scope ok, resolving user…');
        const resolved = await resolveInitBeforeLoadUser({
          config,
          signal: abortController.signal
        });
        if (cancelled) {
          return;
        }
        if (!resolved || !resolved.xmppPassword || !resolved.xmppUsername) {
          devPushLog('error', 'initBeforeLoad: user resolve failed', {
            hasResolved: !!resolved,
            xmppUsername: resolved?.xmppUsername,
            xmppPasswordPresent: !!resolved?.xmppPassword
          });
          setProviderBootstrapStatus('failed');
          inflightBootstrapKeyRef.current = '';
          return;
        }
        devPushLog('rn', `initBeforeLoad: user resolved (${resolved.xmppUsername})`);
        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 = prefetchRoomsViaRest().catch(() => undefined);
        const c = await initializeClient(resolved.xmppUsername, resolved.xmppPassword, config?.xmppSettings);
        if (cancelled) {
          return;
        }
        devPushLog('rn', 'initBeforeLoad: xmpp client created, waiting online…');
        await c.waitForOnline();
        if (cancelled) {
          return;
        }
        devPushLog('xmpp', 'initBeforeLoad: xmpp online');
        try {
          await c.getRoomsStanza();
          devPushLog('xmpp', 'initBeforeLoad: getRoomsStanza ok');
        } catch (e) {
          devPushLog('warn', 'initBeforeLoad: getRoomsStanza failed', e);
        }
        try {
          await c.getChatsPrivateStoreRequestStanza();
          devPushLog('xmpp', 'initBeforeLoad: privateStore ok');
        } catch (e) {
          devPushLog('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(store.getState().rooms.rooms || {}).length;
          devPushLog('xmpp', `initBeforeLoad: joining ${roomCount} rooms via presence…`);
          await allRoomPresences(c.client);
          devPushLog('xmpp', 'initBeforeLoad: allRoomPresences ok');
        } catch (e) {
          devPushLog('warn', 'initBeforeLoad: allRoomPresences failed', e);
        }
        store.dispatch(setStoreClient(c));
        completedBootstrapKeyRef.current = key;
        inflightBootstrapKeyRef.current = '';
        setProviderBootstrapStatus('ready');
        devPushLog('rn', 'initBeforeLoad: READY');

        // Background-prefetch history for every room (fire-and-forget).
        const qos = config?.historyQoS;
        const activeRoomJID = store.getState().rooms.activeRoomJID || null;
        const defaultRoomJids = config?.defaultRooms?.map(r => typeof r === 'string' ? r : r?.jid).filter(Boolean) || [];
        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;
        }
        devPushLog('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
  // -----------------------------------------------------------
  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;
      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`.
  // -----------------------------------------------------------
  useEffect(() => {
    const sub = AppState.addEventListener('change', next => {
      if (next === 'active') {
        return;
      }
      const c = client;
      if (!c) {
        return;
      }
      const state = 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.
  // -----------------------------------------------------------
  useEffect(() => {
    const sub = DeviceEventEmitter.addListener('ethora:retryBootstrap', () => {
      completedBootstrapKeyRef.current = '';
      inflightBootstrapKeyRef.current = '';
      setProviderBootstrapStatus('idle');
    });
    return () => sub.remove();
  }, []);

  // -----------------------------------------------------------
  // Logout event listener (RN equivalent of window event)
  // -----------------------------------------------------------
  useEffect(() => {
    const sub = DeviceEventEmitter.addListener(LOGOUT_EVENT, async () => {
      try {
        if (client) {
          await client.disconnect({
            suppressReconnect: true
          });
        }
      } catch (e) {
        console.warn('Logout disconnect failed', e);
      }
      setGlobalXmppClient(null);
      setClient(null);
      completedBootstrapKeyRef.current = '';
      inflightBootstrapKeyRef.current = '';
      setReconnectAttempts(0);
      setProviderBootstrapStatus('idle');
      await clearPersistedState();
    });
    return () => sub.remove();
  }, [client]);
  return /*#__PURE__*/_jsx(XmppContext.Provider, {
    value: {
      client,
      providerBootstrapStatus,
      initMode,
      initializeClient,
      setClient
    },
    children: children
  });
};
export const useXmppClient = () => {
  const context = 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. */
export function emitXmppLogout() {
  DeviceEventEmitter.emit(LOGOUT_EVENT);
}
//# sourceMappingURL=xmppProvider.js.map