import {
  addReaction,
  listReactionsForMessages,
  type MessageReaction,
  removeReaction,
} from '@chat-app/shared/chat';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { supabase } from './supabase';

export interface AggregatedReaction {
  emoji: string;
  count: number;
  userIds: string[];
  mine: boolean;
}

export interface UseMessageReactionsResult {
  byMessage: Map<string, AggregatedReaction[]>;
  toggle: (messageId: string, emoji: string) => Promise<void>;
  voteExclusive: (messageId: string, emoji: string, exclusiveEmojis: string[]) => Promise<void>;
}

// Batch-fetches reactions for the given message ids + subscribes to the
// message_reactions table. Re-pulls on any change (batch is cheap).
export function useMessageReactions(
  messageIds: string[],
  myId: string | undefined,
): UseMessageReactionsResult {
  const idsKey = useMemo(() => messageIds.join(','), [messageIds]);
  const [rows, setRows] = useState<MessageReaction[]>([]);

  const refresh = useCallback(async () => {
    if (messageIds.length === 0) {
      setRows([]);
      return;
    }
    try {
      const data = await listReactionsForMessages(supabase, messageIds);
      setRows(data);
    } catch (err: unknown) {
      console.error('listReactionsForMessages failed', err);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [idsKey]);

  useEffect(() => {
    void refresh();
    if (messageIds.length === 0) return;

    const channel = supabase
      .channel('reactions:' + idsKey.slice(0, 32))
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'message_reactions' },
        (payload: { new: Record<string, unknown>; old: Record<string, unknown> }) => {
          const mid =
            (payload.new?.message_id as string | undefined) ??
            (payload.old?.message_id as string | undefined);
          if (mid && messageIds.includes(mid)) {
            void refresh();
          }
        },
      )
      .subscribe();

    return () => {
      void supabase.removeChannel(channel);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [idsKey, refresh]);

  const byMessage = useMemo(() => {
    const out = new Map<string, AggregatedReaction[]>();
    for (const r of rows) {
      const list = out.get(r.messageId) ?? [];
      const existing = list.find((a) => a.emoji === r.emoji);
      if (existing) {
        existing.count += 1;
        existing.userIds.push(r.userId);
        if (r.userId === myId) existing.mine = true;
      } else {
        list.push({
          emoji: r.emoji,
          count: 1,
          userIds: [r.userId],
          mine: r.userId === myId,
        });
      }
      out.set(r.messageId, list);
    }
    return out;
  }, [rows, myId]);

  const toggle = useCallback(
    async (messageId: string, emoji: string) => {
      if (!myId) return;
      const current = byMessage.get(messageId) ?? [];
      const existing = current.find((a) => a.emoji === emoji);
      if (existing?.mine) {
        await removeReaction(supabase, messageId, emoji);
      } else {
        await addReaction(supabase, messageId, emoji);
      }
      await refresh();
    },
    [byMessage, myId, refresh],
  );

  const voteExclusive = useCallback(
    async (messageId: string, emoji: string, exclusiveEmojis: string[]) => {
      if (!myId) return;
      const allowed = new Set(exclusiveEmojis);
      const current = (byMessage.get(messageId) ?? []).filter((reaction) =>
        allowed.has(reaction.emoji),
      );
      const selectedMine = current.some((reaction) => reaction.emoji === emoji && reaction.mine);

      for (const reaction of current) {
        if (reaction.mine) {
          await removeReaction(supabase, messageId, reaction.emoji);
        }
      }
      if (!selectedMine) {
        await addReaction(supabase, messageId, emoji);
      }
      await refresh();
    },
    [byMessage, myId, refresh],
  );

  return { byMessage, toggle, voteExclusive };
}
