/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-f1f3d22 | Cube Dev Team */
import { tasty } from "../../../tasty/tasty.js";
import { useEvent } from "../../../_internal/hooks/use-event.js";
import { DisplayTransition } from "../../helpers/DisplayTransition/DisplayTransition.js";
import { Portal } from "../../portal/Portal.js";
import { ToastItem } from "./ToastItem.js";
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";

//#region src/components/overlays/Toast/ToastProvider.tsx
const DEFAULT_DURATION = 5e3;
const MAX_TOASTS = 3;
const COLLAPSE_VISIBLE_HEIGHT = 10;
const CONTAINER_OFFSET = 16;
function generateDedupeKey(data) {
	const id = data.id;
	if (id != null) return String(id);
	return [
		typeof data.title === "string" ? data.title : "",
		typeof data.description === "string" ? data.description : "",
		data.theme ?? "default"
	].join("|");
}
const ToastContext = createContext(null);
function useToastContext() {
	const context = useContext(ToastContext);
	if (!context) throw new Error("useToastContext must be used within a ToastProvider");
	return context;
}
const TOAST_GAP = 8;
const ToastContainerElement = tasty({ styles: {
	position: "fixed",
	top: "2x",
	left: "50%",
	transform: "translateX(-50%)",
	zIndex: 1e4,
	padding: "1x",
	height: "0",
	pointerEvents: "none"
} });
const ToastWrapper = tasty({ styles: {
	position: "absolute",
	top: "0",
	left: "50%",
	transform: "translateX(-50%)",
	width: "max-content 50x",
	pointerEvents: "auto",
	transition: "top 0.3s, opacity 0.3s",
	opacity: {
		"": 0,
		isShown: 1
	}
} });
const DEFAULT_TOAST_HEIGHT = 56;
function ToastContainer({ toasts, onExitComplete }) {
	const [isCollapsed, setIsCollapsed] = useState(false);
	const [heights, setHeights] = useState({});
	const containerRef = useRef(null);
	const boundsRef = useRef(null);
	const toastRefs = useRef(/* @__PURE__ */ new Map());
	const lastPositionsRef = useRef(/* @__PURE__ */ new Map());
	const updateBounds = useCallback(() => {
		const refs = toastRefs.current;
		if (refs.size === 0) {
			boundsRef.current = null;
			return;
		}
		let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
		refs.forEach((el) => {
			const rect = el.getBoundingClientRect();
			minX = Math.min(minX, rect.left);
			minY = Math.min(minY, rect.top);
			maxX = Math.max(maxX, rect.right);
			maxY = Math.max(maxY, rect.bottom);
		});
		if (typeof DOMRect !== "undefined") boundsRef.current = new DOMRect(minX, minY, maxX - minX, maxY - minY);
		else boundsRef.current = {
			x: minX,
			y: minY,
			width: maxX - minX,
			height: maxY - minY,
			top: minY,
			right: maxX,
			bottom: maxY,
			left: minX,
			toJSON: () => ({})
		};
	}, []);
	useEffect(() => {
		if (!isCollapsed) return;
		const handleMouseMove = (e) => {
			const bounds = boundsRef.current;
			if (!bounds) {
				setIsCollapsed(false);
				return;
			}
			const padding = 20;
			if (!(e.clientX >= bounds.left - padding && e.clientX <= bounds.right + padding && e.clientY >= bounds.top - padding && e.clientY <= bounds.bottom + padding)) setIsCollapsed(false);
		};
		window.addEventListener("mousemove", handleMouseMove);
		return () => window.removeEventListener("mousemove", handleMouseMove);
	}, [isCollapsed]);
	useEffect(() => {
		updateBounds();
	}, [toasts, updateBounds]);
	const handleMouseEnter = useCallback(() => {
		updateBounds();
		setIsCollapsed(true);
	}, [updateBounds]);
	useEffect(() => {
		const newHeights = {};
		let hasChanges = false;
		toastRefs.current.forEach((el, id) => {
			const height = el.offsetHeight || DEFAULT_TOAST_HEIGHT;
			newHeights[id] = height;
			if (heights[id] !== height) hasChanges = true;
		});
		for (const id of Object.keys(heights)) if (!toastRefs.current.has(id)) hasChanges = true;
		if (hasChanges) setHeights(newHeights);
	});
	const createRefCallback = useCallback((internalId, displayRef) => (el) => {
		displayRef(el);
		if (el) toastRefs.current.set(internalId, el);
		else toastRefs.current.delete(internalId);
	}, []);
	const visibleToasts = toasts.filter((t) => !t.isExiting);
	const positions = useMemo(() => {
		const posMap = /* @__PURE__ */ new Map();
		let currentTop = 0;
		for (const toast of visibleToasts) {
			posMap.set(toast.internalId, currentTop);
			const height = heights[toast.internalId] ?? DEFAULT_TOAST_HEIGHT;
			currentTop += height + TOAST_GAP;
		}
		return posMap;
	}, [visibleToasts, heights]);
	useEffect(() => {
		positions.forEach((pos, id) => {
			lastPositionsRef.current.set(id, pos);
		});
	}, [positions]);
	const handleExitComplete = useCallback((internalId) => {
		lastPositionsRef.current.delete(internalId);
		onExitComplete(internalId);
	}, [onExitComplete]);
	const getToastStyle = useCallback((toast, index, total) => {
		const baseTop = positions.get(toast.internalId) ?? lastPositionsRef.current.get(toast.internalId) ?? 0;
		const height = heights[toast.internalId] ?? DEFAULT_TOAST_HEIGHT;
		if (!isCollapsed) return { top: `${baseTop}px` };
		const isNewest = index === total - 1;
		return {
			top: `${COLLAPSE_VISIBLE_HEIGHT - CONTAINER_OFFSET - height}px`,
			zIndex: index,
			opacity: isNewest ? 1 : 0,
			pointerEvents: "none"
		};
	}, [
		isCollapsed,
		positions,
		heights
	]);
	if (toasts.length === 0) return null;
	return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(ToastContainerElement, {
		ref: containerRef,
		children: toasts.map((toast) => {
			const visibleIndex = visibleToasts.findIndex((t) => t.internalId === toast.internalId);
			return /* @__PURE__ */ jsx(DisplayTransition, {
				animateOnMount: true,
				isShown: !toast.isExiting,
				onRest: (transition) => {
					if (transition === "exit") handleExitComplete(toast.internalId);
				},
				children: ({ isShown, ref }) => /* @__PURE__ */ jsx(ToastWrapper, {
					ref: createRefCallback(toast.internalId, ref),
					mods: { isShown },
					style: getToastStyle(toast, visibleIndex >= 0 ? visibleIndex : 0, visibleToasts.length),
					onMouseEnter: handleMouseEnter,
					children: /* @__PURE__ */ jsx(ToastItem, {
						...toast.itemProps,
						title: toast.title,
						description: toast.description,
						theme: toast.theme,
						icon: toast.icon,
						isLoading: toast.isLoading
					})
				})
			}, toast.internalId);
		})
	}) });
}
function ToastProvider({ children }) {
	const [toasts, setToasts] = useState([]);
	const timersRef = useRef(/* @__PURE__ */ new Map());
	const idCounter = useRef(0);
	const generateId = useCallback(() => {
		idCounter.current += 1;
		return `toast-${idCounter.current}-${Date.now()}`;
	}, []);
	const clearTimer = useCallback((internalId) => {
		const timer = timersRef.current.get(internalId);
		if (timer) {
			clearTimeout(timer);
			timersRef.current.delete(internalId);
		}
	}, []);
	const removeToast = useEvent((id) => {
		setToasts((prev) => {
			const toast = prev.find((t) => t.id === id || t.internalId === String(id));
			if (toast) clearTimer(toast.internalId);
			return prev.map((t) => {
				if (t.id === id || t.internalId === String(id)) return {
					...t,
					isExiting: true
				};
				return t;
			});
		});
	});
	const finalizeRemoval = useEvent((internalId) => {
		setToasts((prev) => prev.filter((t) => t.internalId !== internalId));
	});
	const addToast = useEvent((data, isProgress = false) => {
		const internalId = generateId();
		const dedupeKey = generateDedupeKey(data);
		const duration = isProgress ? null : data.duration ?? DEFAULT_DURATION;
		setToasts((prev) => {
			const existingIndex = prev.findIndex((t) => t.dedupeKey === dedupeKey && !t.isExiting);
			if (existingIndex !== -1) {
				const existing = prev[existingIndex];
				clearTimer(existing.internalId);
				return prev.map((t, i) => {
					if (i === existingIndex) return {
						...t,
						isExiting: true
					};
					return t;
				});
			}
			return prev;
		});
		const newToast = {
			...data,
			internalId,
			isProgress,
			dedupeKey,
			createdAt: Date.now()
		};
		setToasts((prev) => {
			let newToasts = [...prev, newToast];
			const activeToasts = newToasts.filter((t) => !t.isExiting);
			const progressToasts = activeToasts.filter((t) => t.isProgress);
			const temporalToasts = activeToasts.filter((t) => !t.isProgress);
			while (activeToasts.length > MAX_TOASTS) if (temporalToasts.length > 1 || progressToasts.length < MAX_TOASTS) {
				const oldestTemporal = temporalToasts.shift();
				if (oldestTemporal) {
					clearTimer(oldestTemporal.internalId);
					newToasts = newToasts.map((t) => {
						if (t.internalId === oldestTemporal.internalId) return {
							...t,
							isExiting: true
						};
						return t;
					});
					const idx = activeToasts.findIndex((t) => t.internalId === oldestTemporal.internalId);
					if (idx !== -1) activeToasts.splice(idx, 1);
				} else break;
			} else break;
			return newToasts;
		});
		if (duration != null && duration > 0) {
			const timer = setTimeout(() => {
				removeToast(data.id ?? internalId);
			}, duration);
			timersRef.current.set(internalId, timer);
		}
		return data.id ?? internalId;
	});
	const updateToast = useEvent((id, data) => {
		setToasts((prev) => prev.map((t) => {
			if (t.id === id || t.internalId === String(id)) return {
				...t,
				...data
			};
			return t;
		}));
	});
	useEffect(() => {
		return () => {
			timersRef.current.forEach((timer) => clearTimeout(timer));
			timersRef.current.clear();
		};
	}, []);
	const contextValue = useMemo(() => ({
		addToast,
		removeToast,
		updateToast,
		toasts
	}), [
		addToast,
		removeToast,
		updateToast,
		toasts
	]);
	return /* @__PURE__ */ jsxs(ToastContext.Provider, {
		value: contextValue,
		children: [children, /* @__PURE__ */ jsx(ToastContainer, {
			toasts,
			onExitComplete: finalizeRemoval
		})]
	});
}

//#endregion
export { ToastProvider, useToastContext };
//# sourceMappingURL=ToastProvider.js.map