import { useEffect, useState } from 'react';

import { supabase } from './supabase';

// Watches the `call-presence:<conversationId>` realtime channel and returns
// the set of userIds currently in that call. Independent of whether the
// viewer is in the room — used to show "Active call · Join" affordances.
//
// Why presence-callbacks + no-removeChannel:
//   - Supabase realtime 2.103+ dedupes channels by topic. CallContext owns
//     the same channel during a live call to track its own presence.
//   - If this hook removed the channel on unmount (e.g. when ConversationHeader
//     hides during an active call), the live tracking connection dies and the
//     banner never shows the rejoin option afterwards.
//   - Binding our own presence callbacks (sync/join/leave) ensures the local
//     presence state is updated even when CallContext also listens on the same
//     channel — multiple callbacks can co-exist.
export function useCallPresence(conversationId: string | undefined): string[] {
  const [activeUserIds, setActiveUserIds] = useState<string[]>([]);

  useEffect(() => {
    if (!conversationId) {
      setActiveUserIds([]);
      return;
    }

    const channel = supabase.channel('call-presence:' + conversationId, {
      config: {
        presence: {
          key: 'observer-' + Math.random().toString(36).slice(2, 8),
          // Server only routes presence to channels that opt-in. Without this
          // flag we'd receive zero presence_state/presence_diff messages.
          enabled: true,
        },
      },
    });

    const resync = () => {
      const state = channel.presenceState() as Record<string, Array<Record<string, unknown>>>;
      const ids = new Set<string>();
      for (const list of Object.values(state)) {
        for (const entry of list) {
          const uid = entry?.userId;
          if (typeof uid === 'string') ids.add(uid);
        }
      }
      setActiveUserIds((prev) => {
        const next = Array.from(ids).sort();
        if (prev.length === next.length && prev.every((v, i) => v === next[i])) return prev;
        return next;
      });
    };

    // Supabase realtime forbids `.on()` after `.subscribe()`. So we only bind
    // callbacks when we own the subscribe (channel was 'closed'). For deduped
    // shared channels, the polling fallback below covers state changes.
    if (channel.state === 'closed') {
      channel
        .on('presence', { event: 'sync' }, resync)
        .on('presence', { event: 'join' }, resync)
        .on('presence', { event: 'leave' }, resync);
      void channel.subscribe();
    }

    // Initial poll covers the case where the channel is already 'joined' (we
    // dedupe'd onto an existing subscription) and won't fire a fresh sync.
    resync();
    // Belt-and-suspenders: a slow poll catches any drift between callback and
    // server state (e.g. if a presence event was dropped during reconnect).
    const pollId = window.setInterval(resync, 3000);

    return () => {
      window.clearInterval(pollId);
      // Don't removeChannel — the channel is shared with CallContext (and any
      // sibling observers) via Supabase's topic dedupe. Removing it would
      // tear down a live call's presence tracker. The channel naturally
      // outlives the hook; leaks are bounded by the small number of
      // conversations a user holds open in a session.
    };
  }, [conversationId]);

  return activeUserIds;
}
