import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import socket from "@/lib/socket.io";

type RoomParams = Record<string, unknown>;

type JoinRoomPayload = {
	room: string;
	params?: RoomParams;
};

type RoomUpdatePayload<T> = {
	roomId: string;
	payloads: T[];
	type: string;
};

type RoomJoinedPayload = {
	room: string;
	roomId: string;
};

type RoomErrorPayload = {
	room?: string;
	roomId?: string;
	message?: string;
};

export function useSocketRoom<T>(room: string, params?: RoomParams) {
	const [actualRoomId, setActualRoomId] = useState<string | null>(null);
	const [data, setData] = useState<T[]>([]);
	const [info, setInfo] = useState(
		"No data yet — join the room to start receiving",
	);

	// This is the payload we send to the server.
	// Example:
	// { room: "inventory", params: { location: "ppoo" } }
	const joinPayload = useMemo<JoinRoomPayload>(
		() => ({
			room,
			params,
		}),
		[room, params],
	);

	const clearRoom = useCallback((filterFn?: (item: T) => boolean) => {
		if (filterFn) {
			setData((prev) => prev.filter((item) => !filterFn(item)));
			return;
		}

		setData([]);
		setInfo("Room data cleared");
	}, []);

	useEffect(() => {
		// Join the logical room.
		// The server decides the real Socket.IO roomId.
		// Example:
		// client sends: { room: "inventory", params: { location: "ppoo" } }
		// server joins: "inventory:ppoo"
		function joinRoom() {
			socket.emit("join-room", joinPayload);
			setInfo(`Joining room: ${room}`);
		}

		// Server should emit this after socket.join(actualRoom).
		// This lets the client know the final roomId to filter updates by.
		function handleJoined(payload: RoomJoinedPayload) {
			//if (payload.room !== room) return;

			setActualRoomId(payload.roomId);
			setInfo(`Joined room: ${payload.roomId}`);
		}

		function handleUpdate(payload: RoomUpdatePayload<T>) {
			// If we know the actual roomId, only accept updates for that room.
			// This protects against other pages/rooms also listening to "room-update".

			if (!actualRoomId) return;

			if (payload.roomId !== actualRoomId) return;

			if (payload.type === "snapshot") {
				setData(payload.payloads);
				return;
			}

			// Append mode is good for logs/scans/events.
			setData((prev) => [...payload.payloads, ...prev]);
			setInfo("");
		}

		function handleError(err: RoomErrorPayload) {
			// Ignore errors for other logical rooms.
			if (err.room && err.room !== room) return;

			// Ignore errors for other actual rooms.
			if (err.roomId && room && err.roomId !== room) return;

			toast.error(err.message);
			setInfo(err.message ?? "Room error");
		}

		socket.on("connect", joinRoom);
		socket.on("room-joined", handleJoined);
		socket.on("room-update", handleUpdate);
		socket.on("room-error", handleError);

		if (!socket.connected && socket.disconnected) {
			socket.connect();
		}

		// If socket is already connected, join immediately.
		if (socket.connected) {
			joinRoom();
		}

		return () => {
			// Leave using the same logical room payload.
			// Server should rebuild the actual room and call socket.leave(actualRoom).
			socket.emit("leave-room", joinPayload);

			socket.off("connect", joinRoom);
			socket.off("room-joined", handleJoined);
			socket.off("room-update", handleUpdate);
			socket.off("room-error", handleError);
		};
	}, [room, joinPayload, actualRoomId]);

	return {
		data,
		info,
		clearRoom,
	};
}
