"use client";

import { io, Socket } from "socket.io-client";
import { createStore, useStore } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";

import { devLogger } from "@/shared/utils/logger";

import type { ClientToServerEvents, ServerToClientEvents } from "./events";

export enum ESocketStatus {
  CONNECTING = "connecting",
  CONNECTED = "connected",
  DISCONNECTED = "disconnected",
  ERROR = "error",
}

interface SocketState {
  socket: Socket<ServerToClientEvents, ClientToServerEvents> | null;
  status: ESocketStatus;
  error: string | null;
  reconnectAttempt: number;
  // queue: { event: keyof ClientToServerEvents; args: unknown[] }[];
  reconnectTimeoutId?: ReturnType<typeof setTimeout>;
}

interface SocketActions {
  connect: (userId: string) => void;
  disconnect: () => void;
  emit: <E extends keyof ClientToServerEvents>(
    event: E,
    ...args: Parameters<ClientToServerEvents[E]>
  ) => void;
  clearReconnectTimer: () => void;

  // enqueue: <E extends keyof ClientToServerEvents>(
  //   event: E,
  //   ...args: Parameters<ClientToServerEvents[E]>
  // ) => void;
  // flushQueue: () => void;
}

export type SocketStore = SocketState & SocketActions;

const MAX_RECONNECT = 6;
const BASE_DELAY = 2000;

const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL ?? "";
if (!SOCKET_URL) {
  // eslint-disable-next-line no-console
  console.error("NEXT_PUBLIC_SOCKET_URL env-variable is not set");
}

function expDelay(attempt: number) {
  return Math.min(BASE_DELAY * 2 ** attempt, 30_000);
}

export const useSocketStore = createStore<SocketStore>()(
  subscribeWithSelector((set, get) => ({
    socket: null,
    status: ESocketStatus.DISCONNECTED,
    error: null,
    reconnectAttempt: 0,
    reconnectTimeoutId: undefined,
    // queue: [],

    clearReconnectTimer: () => {
      const id = get().reconnectTimeoutId;
      if (id) clearTimeout(id);
      set({ reconnectTimeoutId: undefined });
    },

    connect: (userId: string) => {
      if (get().socket?.connected || !SOCKET_URL) return;

      get().clearReconnectTimer();
      set({
        status: ESocketStatus.CONNECTING,
        error: null,
      });

      const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
        SOCKET_URL,
        {
          query: { userId },
          transports: ["websocket"],
          autoConnect: false,
        }
      );

      const cleanListeners = () => {
        socket.removeAllListeners();
      };

      // Universal incoming event logger
      socket.onAny((event: string, ...args: unknown[]) => {
        // Skip logging built-in socket.io events to avoid noise
        const systemEvents = [
          "connect",
          "disconnect",
          "connect_error",
          "reconnect",
        ];
        if (!systemEvents.includes(event)) {
          devLogger.socketIncoming(event, ...args);
        }
      });

      socket.on("connect", () => {
        devLogger.socketConnected();
        set({ status: ESocketStatus.CONNECTED, reconnectAttempt: 0, socket });
        get().clearReconnectTimer();
        // get().flushQueue();
      });

      socket.on("disconnect", reason => {
        devLogger.socketDisconnected(reason);
        set({ status: ESocketStatus.DISCONNECTED });
        cleanListeners();
        scheduleReconnect();
      });

      socket.on("connect_error", err => {
        devLogger.socketError(err);
        set({ status: ESocketStatus.ERROR, error: err.message });
        scheduleReconnect();
      });

      socket.connect();
      set({ socket });

      function scheduleReconnect() {
        const attempt = get().reconnectAttempt + 1;
        if (attempt > MAX_RECONNECT) return;
        set({ reconnectAttempt: attempt });

        /* CHANGE: сохраняем id таймера в state для дальнейшего clear */
        const id = setTimeout(() => get().connect(userId), expDelay(attempt));
        set({ reconnectTimeoutId: id });
      }
    },

    disconnect: () => {
      get().clearReconnectTimer();
      get().socket?.disconnect();
      set({
        socket: null,
        status: ESocketStatus.DISCONNECTED,
        error: null,
        reconnectAttempt: 0,
        // queue: [],
      });
    },

    emit: (event, ...args) => {
      const { socket, status } = get();
      if (status === ESocketStatus.CONNECTED && socket && socket.connected) {
        devLogger.socketOutgoing(event as string, ...args);
        socket.emit(event, ...args);
      } else {
        // get().enqueue(event, ...args);
        // eslint-disable-next-line no-console
        console.warn("NO SOCKET, emit not sent", event, args);
      }
    },

    // enqueue: (event, ...args) =>
    //   set(state => ({
    //     queue: [...state.queue, { event, args }],
    //   })),

    // flushQueue: () => {
    //   const { queue, socket } = get();
    //   if (!socket) return;
    //   queue.forEach(({ event, args }) => {
    //     socket.emit(event, ...args);
    //   });
    //   set({ queue: [] });
    // },
  }))
);

export const useSocketStatus = () =>
  useStore(useSocketStore, state => state.status);
export const useSocketEmit = () =>
  useStore(useSocketStore, state => state.emit);
export const useSocketEntity = () =>
  useStore(useSocketStore, state => state.socket);
