// src/client/shared/api/SocketService.ts
// Singleton Socket Service with Batching for real-time events

import { io, type Socket } from 'socket.io-client';

const SOCKET_URL = import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000';

/**
 * Тип колбэка для обработки событий сокета
 */
export type EventCallback<T = any> = (data: T) => void;

/**
 * Тип колбэка для обработки пакетных событий
 */
export type BatchedEventCallback<T = any> = (events: T[]) => void;

/**
 * Конфигурация для пакетной обработки событий
 */
export interface BatchConfig<T = any> {
  /**
   * Таймаут в миллисекундах для накопления событий (по умолчанию 50мс)
   */
  debounceMs?: number;
  /**
   * Колбэк для обработки пакета событий
   */
  callback: BatchedEventCallback<T>;
}

/**
 * Синглтон сервис для управления WebSocket соединением
 */
class SocketServiceClass {
  private socket: Socket | null = null;
  private isConnected = false;
  private eventListeners: Map<string, Set<EventCallback>> = new Map();
  private batchedListeners: Map<string, BatchConfig> = new Map();
  private batchTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
  private batchBuffers: Map<string, any[]> = new Map();

  /**
   * Инициализирует соединение с сервером
   */
  connect(): void {
    if (this.socket?.connected) {
      console.log('[SocketService] Already connected:', this.socket.id);
      return;
    }

    this.socket = io(SOCKET_URL, {
      withCredentials: true,
      transports: ['websocket'],
      autoConnect: false,
    });

    // Логирование событий соединения
    this.socket.on('connect', () => {
      this.isConnected = true;
      console.log('[SocketService] Connected:', this.socket?.id);
    });

    this.socket.on('disconnect', (reason) => {
      this.isConnected = false;
      console.log('[SocketService] Disconnected:', reason);
    });

    this.socket.on('connect_error', (error) => {
      this.isConnected = false;
      console.error('[SocketService] Connection error:', error);
    });

    // Логирование всех входящих событий (для дебага)
    this.socket.onAny((event, ...args) => {
      console.log(`[SocketService] Received: ${event}`, args);
    });

    // Подключаемся
    this.socket.connect();
  }

  /**
   * Отключает соединение с сервером
   */
  disconnect(): void {
    if (this.socket?.connected) {
      this.socket.disconnect();
      this.isConnected = false;
      console.log('[SocketService] Disconnected');
    }

    // Очищаем все слушатели
    this.eventListeners.clear();
    this.batchedListeners.clear();
    this.batchTimers.forEach(timer => clearTimeout(timer));
    this.batchTimers.clear();
    this.batchBuffers.clear();
  }

  /**
   * Проверяет, установлено ли соединение
   */
  isSocketConnected(): boolean {
    return this.isConnected && this.socket?.connected === true;
  }

  /**
   * Подписывается на событие сокета
   * @param event - имя события
   * @param callback - колбэк для обработки события
   * @returns функция отписки
   */
  subscribe<T = any>(event: string, callback: EventCallback<T>): () => void {
    if (!this.socket) {
      console.warn('[SocketService] Socket not initialized. Call connect() first.');
      return () => {};
    }

    // Добавляем колбэк в список слушателей
    if (!this.eventListeners.has(event)) {
      this.eventListeners.set(event, new Set());
      // Подписываемся на событие сокета
      this.socket.on(event, (data) => {
        const listeners = this.eventListeners.get(event);
        if (listeners) {
          listeners.forEach(cb => cb(data));
        }
      });
    }

    this.eventListeners.get(event)!.add(callback);

    // Возвращаем функцию отписки
    return () => {
      const listeners = this.eventListeners.get(event);
      if (listeners) {
        listeners.delete(callback);
        // Если больше нет слушателей, отписываемся от события сокета
        if (listeners.size === 0) {
          this.socket?.off(event);
          this.eventListeners.delete(event);
        }
      }
    };
  }

  /**
   * Подписывается на событие с пакетной обработкой
   * События накапливаются в течение debounceMs и передаются колбэку одним массивом
   * @param event - имя события
   * @param config - конфигурация пакетной обработки
   * @returns функция отписки
   */
  subscribeBatched<T = any>(event: string, config: BatchConfig<T>): () => void {
    const debounceMs = config.debounceMs ?? 50;

    // Сохраняем конфигурацию
    this.batchedListeners.set(event, config);

    // Инициализируем буфер
    if (!this.batchBuffers.has(event)) {
      this.batchBuffers.set(event, []);
    }

    // Подписываемся на событие сокета
    const unsubscribe = this.subscribe<T>(event, (data) => {
      // Добавляем событие в буфер
      const buffer = this.batchBuffers.get(event)!;
      buffer.push(data);

      // Сбрасываем таймер
      const existingTimer = this.batchTimers.get(event);
      if (existingTimer) {
        clearTimeout(existingTimer);
      }

      // Устанавливаем новый таймер
      const timer = setTimeout(() => {
        // Вызываем колбэк с накопленными событиями
        const events = buffer.splice(0); // Очищаем буфер
        if (events.length > 0) {
          config.callback(events);
        }
      }, debounceMs);

      this.batchTimers.set(event, timer);
    });

    // Возвращаем функцию отписки
    return () => {
      unsubscribe();
      this.batchedListeners.delete(event);
      const timer = this.batchTimers.get(event);
      if (timer) {
        clearTimeout(timer);
        this.batchTimers.delete(event);
      }
      this.batchBuffers.delete(event);
    };
  }

  /**
   * Отправляет событие на сервер
   * @param event - имя события
   * @param data - данные для отправки
   */
  emit<T = any>(event: string, data?: T): void {
    if (!this.socket?.connected) {
      console.warn('[SocketService] Socket not connected. Cannot emit event:', event);
      return;
    }
    this.socket.emit(event, data);
  }

  /**
   * Возвращает экземпляр сокета (для расширенного использования)
   */
  getSocket(): Socket | null {
    return this.socket;
  }
}

/**
 * Синглтон экземпляр SocketService
 */
export const socketService = new SocketServiceClass();

/**
 * Хук для использования SocketService в компонентах Vue
 * Автоматически отписывается при размонтировании компонента
 */
export function useSocket() {
  const subscriptions: Array<() => void> = [];

  const subscribe = <T = any>(event: string, callback: EventCallback<T>) => {
    const unsubscribe = socketService.subscribe(event, callback);
    subscriptions.push(unsubscribe);
  };

  const subscribeBatched = <T = any>(event: string, config: BatchConfig<T>) => {
    const unsubscribe = socketService.subscribeBatched(event, config);
    subscriptions.push(unsubscribe);
  };

  // Очистка всех подписок при размонтировании
  const cleanup = () => {
    subscriptions.forEach(unsubscribe => unsubscribe());
    subscriptions.length = 0;
  };

  return {
    socketService,
    subscribe,
    subscribeBatched,
    cleanup,
    isConnected: () => socketService.isSocketConnected(),
  };
}
