'use client';

import * as React from 'react';
import { useAuth } from '@/components/id/auth-provider';
import { getWsUrl, type ChatMessage } from '@/lib/api';
import { dispatchAvatarUpdated, dispatchChatRoomAvatarUpdated } from '@/lib/avatar-events';

export interface RealtimeEvent {
  userId?: string;
  type: string;
  title: string;
  message: string;
  payload?: Record<string, unknown> & {
    notificationId?: string;
    roomId?: string;
    message?: ChatMessage;
    inviteId?: string;
    groupId?: string;
  };
}

type Listener = (event: RealtimeEvent) => void;

interface RealtimeContextValue {
  unreadCount: number;
  setUnreadCount: React.Dispatch<React.SetStateAction<number>>;
  subscribe: (listener: Listener) => () => void;
  connected: boolean;
}

const RealtimeContext = React.createContext<RealtimeContextValue | null>(null);

export function RealtimeProvider({ children }: { children: React.ReactNode }) {
  const { user, token, isPinLocked } = useAuth();
  const [unreadCount, setUnreadCount] = React.useState(0);
  const [connected, setConnected] = React.useState(false);
  const listenersRef = React.useRef(new Set<Listener>());
  const socketRef = React.useRef<WebSocket | null>(null);
  const reconnectTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);

  const subscribe = React.useCallback((listener: Listener) => {
    listenersRef.current.add(listener);
    return () => listenersRef.current.delete(listener);
  }, []);

  const emit = React.useCallback((event: RealtimeEvent) => {
    listenersRef.current.forEach((listener) => listener(event));
  }, []);

  React.useEffect(() => {
    if (!user || !token || isPinLocked) {
      socketRef.current?.close();
      socketRef.current = null;
      setConnected(false);
      return;
    }

    let cancelled = false;

    function connect() {
      if (cancelled) return;
      const url = `${getWsUrl()}?token=${encodeURIComponent(token!)}`;
      const socket = new WebSocket(url);
      socketRef.current = socket;

      socket.onopen = () => setConnected(true);
      socket.onclose = () => {
        setConnected(false);
        if (!cancelled) {
          reconnectTimer.current = setTimeout(connect, 3000);
        }
      };
      socket.onerror = () => socket.close();
      socket.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data as string) as RealtimeEvent;
          emit(data);
          if (data.type === 'user_avatar_updated' && typeof data.payload?.userId === 'string') {
            dispatchAvatarUpdated(data.payload.userId);
          }
          if (data.type === 'chat_message') {
            const message = data.payload?.message;
            if (message?.type === 'SYSTEM' && message.roomId && message.metadataJson) {
              try {
                const metadata = JSON.parse(message.metadataJson) as { systemKind?: string };
                if (metadata.systemKind === 'avatar_changed') {
                  dispatchChatRoomAvatarUpdated(message.roomId);
                }
              } catch {
                // ignore malformed metadata
              }
            }
          }
          if (
            data.type === 'family_invite' ||
            data.type === 'role_assigned' ||
            data.type === 'role_removed' ||
            (data.type === 'chat_message' && data.payload?.notificationId)
          ) {
            setUnreadCount((count) => count + 1);
          }
        } catch {
          // ignore malformed payloads
        }
      };
    }

    connect();

    return () => {
      cancelled = true;
      if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
      socketRef.current?.close();
      socketRef.current = null;
      setConnected(false);
    };
  }, [emit, isPinLocked, token, user]);

  return (
    <RealtimeContext.Provider value={{ unreadCount, setUnreadCount, subscribe, connected }}>
      {children}
    </RealtimeContext.Provider>
  );
}

export function useRealtime() {
  const context = React.useContext(RealtimeContext);
  if (!context) {
    throw new Error('useRealtime должен использоваться внутри RealtimeProvider');
  }
  return context;
}
