import { HubConnection } from "@microsoft/signalr";
import React, { useCallback, useEffect, useMemo, useState } from "react";

import { NOTIFICATION_METHOD } from "../../constants";

import { createHubConnection } from "./createHubConnection";
import { SignalRContext } from "./SignalRContext";

interface Subscription {
	method: string;
	value: unknown;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Action = (entity: any) => void;

export const FAILED_CONNECTION_ERROR = "Connection is not created";

export function SignalRProvider({ children }: { children: React.ReactNode }) {
	const [connection, setConnection] = useState<HubConnection | null>(null);
	const [connected, setConnected] = useState(false);
	const subscriptions = useMemo<Map<string, Action[]>>(
		() => new Map<string, Action[]>(),
		[],
	);

	const handleEvent = useCallback(
		(entity: Subscription) => {
			subscriptions.get(entity.method)?.forEach((callback) => {
				callback(entity.value);
			});
		},
		[subscriptions],
	);

	useEffect(() => {
		const conn = createHubConnection();
		setConnection(conn);

		let canceled = false;
		let retryCount = 0;
		const maxRetries = 3;

		const startConnection = async () => {
			try {
				await conn.start();
				if (canceled) return;
				setConnected(true);

				conn.on(NOTIFICATION_METHOD, handleEvent);
			} catch (e) {
				console.warn("SignalR connection failed:", e);

				if (!canceled && retryCount < maxRetries) {
					retryCount++;
					setTimeout(() => startConnection(), 2000 * retryCount);
				} else {
					console.warn(
						"SignalR connection unavailable. App will work without real-time updates.",
					);
				}
			}
		};

		startConnection();

		return () => {
			canceled = true;
			setConnected(false);
			conn.stop().catch(() => {});
		};
	}, [handleEvent]);

	/**
	 *
	 * @param {string} method Method SignalR. Example: "USERS.CREATED".
	 * @param {(entity)=>void} callback Callback with one parameter. Method must not return anything.
	 */
	const subscribe = useCallback(
		(method: string, callback: Action) => {
			const list = subscriptions.get(method) || [];
			list.push(callback);
			subscriptions.set(method, list);
		},
		[subscriptions],
	);

	const unsubscribe = useCallback(
		(method: string, callback: Action) => {
			const list = subscriptions.get(method) || [];
			const index = list.indexOf(callback);
			list.splice(index, 1);
			subscriptions.set(method, list);
		},
		[subscriptions],
	);

	const value = useMemo(
		() => ({
			connection,
			connected,
			subscribe,
			unsubscribe,
		}),
		[connection, connected, subscribe, unsubscribe],
	);

	return (
		<SignalRContext.Provider value={value}>
			{children}
		</SignalRContext.Provider>
	);
}
