import type { ChatMessage } from '../types';

import type { ChatNotifier } from './types';

export interface CrossTabNotifierOptions {
  /**
   * The underlying notifier that performs the actual title/favicon
   * mutation (usually `createBrowserNotifier()`). The decorator only
   * forwards calls when this tab is the elected leader; followers stay
   * silent so the title doesn't flicker in stereo across 4 open tabs.
   */
  inner: ChatNotifier;
  /**
   * Live "this tab is the leader" flag. Read from `useActiveTab` in
   * the hook layer and passed in via the live-getter form so the
   * notifier sees the latest value without React re-rendering it.
   */
  isLeader: () => boolean;
  /**
   * Optional broadcast channel name. Followers subscribe to count
   * updates here so the in-tab badge UI (FAB counter) stays in sync
   * even when they're not the one mutating the title.
   *
   * Default: `djangocfg-chat:unread`.
   */
  channel?: string;
  /**
   * Callback for follower tabs: fired when a peer (leader) reports an
   * unread count update. Use it to drive a Zustand store that the FAB
   * badge subscribes to.
   */
  onPeerUpdate?: (count: number) => void;
}

interface BroadcastPayload {
  count: number;
}

const DEFAULT_CHANNEL = 'djangocfg-chat:unread';

/**
 * Decorator over an inner notifier that adds cross-tab coordination.
 *
 * Behaviour:
 *   - Leader tab → forwards `setUnread/clear` to the inner notifier
 *     AND broadcasts the count so follower tabs can update their FAB
 *     badge UI.
 *   - Follower tab → does NOT call inner (silent on title/favicon),
 *     but still broadcasts via `onPeerUpdate` so the host store
 *     reflects the truth.
 *
 * When leadership flips at runtime (e.g. the previous leader closed
 * its tab), the next `setUnread` call from the new leader will start
 * mutating the title — there's no special handoff needed because the
 * inner notifier is idempotent.
 */
export function createCrossTabNotifier(opts: CrossTabNotifierOptions): ChatNotifier {
  const { inner, isLeader, onPeerUpdate } = opts;
  const channelName = opts.channel ?? DEFAULT_CHANNEL;

  let channel: BroadcastChannel | null = null;
  if (typeof BroadcastChannel !== 'undefined') {
    channel = new BroadcastChannel(channelName);
    if (onPeerUpdate) {
      channel.addEventListener('message', (e) => {
        const data = e.data as BroadcastPayload | undefined;
        if (!data || typeof data.count !== 'number') return;
        onPeerUpdate(data.count);
      });
    }
  }

  return {
    setUnread(count: number, latest?: ChatMessage | null) {
      // Broadcast first so followers learn the count even if our local
      // inner notifier no-ops in this environment.
      channel?.postMessage({ count } satisfies BroadcastPayload);
      if (isLeader()) {
        inner.setUnread(count, latest);
      }
    },
    clear() {
      channel?.postMessage({ count: 0 } satisfies BroadcastPayload);
      if (isLeader()) {
        inner.clear();
      } else {
        // Followers never armed the inner notifier, but call clear()
        // anyway in case leadership flipped between an old setUnread
        // and this clear — inner is idempotent.
        inner.clear();
      }
    },
    dispose() {
      channel?.close();
      channel = null;
      inner.dispose?.();
    },
  };
}
