import React, { useCallback, useEffect, useMemo, useRef, type ReactElement } from 'react';
import { type CallEmbed } from '../../plugins/call';
import { useCallMembers, useCallSession } from '../../hooks/useCall';
import { useAvatarDecoration } from '../../hooks/useAvatarDecoration';
import { decorationUrl } from './avatarDecorations';

/**
 * [lotus #6] Pushes each call participant's avatar-decoration image URL to the
 * forked Element Call (`io.lotus.decorations`), which renders it on the in-call
 * video-tile avatars. Mounted only while joined, so the EC-side handler exists.
 *
 * The decoration roster is per-user slugs resolved via `useAvatarDecoration`;
 * we render one invisible probe per member to reuse that hook + its cache, then
 * debounce-send the aggregated `{ userId: url }` map whenever it changes.
 */
function DecorationProbe({
  userId,
  onResolve,
}: {
  userId: string;
  onResolve: (userId: string, url: string | null) => void;
}): null {
  const slug = useAvatarDecoration(userId);
  useEffect(() => {
    onResolve(userId, slug ? decorationUrl(slug) : null);
  }, [userId, slug, onResolve]);
  return null;
}

export function LotusDecorationPusher({ callEmbed }: { callEmbed: CallEmbed }): ReactElement {
  const session = useCallSession(callEmbed.room);
  const members = useCallMembers(session);
  const map = useRef<Map<string, string>>(new Map());
  const pushTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  const userIds = useMemo(
    () => Array.from(new Set(members.map((m) => m.userId).filter((u): u is string => !!u))),
    [members],
  );

  const push = useCallback(() => {
    const decorations: Record<string, string> = {};
    map.current.forEach((url, userId) => {
      decorations[userId] = url;
    });
    callEmbed.call.transport.send('io.lotus.decorations', { decorations }).catch(() => undefined);
  }, [callEmbed]);

  const schedulePush = useCallback(() => {
    if (pushTimer.current) clearTimeout(pushTimer.current);
    pushTimer.current = setTimeout(push, 300);
  }, [push]);

  const onResolve = useCallback(
    (userId: string, url: string | null) => {
      const prev = map.current.get(userId);
      if (url) {
        if (prev !== url) {
          map.current.set(userId, url);
          schedulePush();
        }
      } else if (prev !== undefined) {
        map.current.delete(userId);
        schedulePush();
      }
    },
    [schedulePush],
  );

  // Drop decorations for participants who left the call.
  useEffect(() => {
    const present = new Set(userIds);
    let changed = false;
    map.current.forEach((_url, userId) => {
      if (!present.has(userId)) {
        map.current.delete(userId);
        changed = true;
      }
    });
    if (changed) schedulePush();
  }, [userIds, schedulePush]);

  useEffect(
    () => () => {
      if (pushTimer.current) clearTimeout(pushTimer.current);
    },
    [],
  );

  return (
    <>
      {userIds.map((userId) => (
        <DecorationProbe key={userId} userId={userId} onResolve={onResolve} />
      ))}
    </>
  );
}
