/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-d4ad2ea | Cube Dev Team */
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";

//#region src/components/helpers/DisplayTransition/DisplayTransition.tsx
const AUTO_FALLBACK_DURATION = 500;
function useEvent(fn) {
	const ref = useRef(fn);
	useLayoutEffect(() => {
		ref.current = fn;
	}, [fn]);
	return useCallback((...args) => {
		return ref.current?.(...args);
	}, []);
}
function DisplayTransition({ isShown: targetShown, duration, onRest, onPhaseChange, onToggle, exposeUnmounted = false, animateOnMount = true, respectReducedMotion = true, preserveContent = true, children }) {
	const dur = respectReducedMotion && typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ? 0 : duration;
	const storedChildrenRef = useRef(children);
	const elementRef = useRef(null);
	const transitionStartedRef = useRef(false);
	const eventListenersRef = useRef(null);
	const [phase, setPhase] = useState(targetShown ? animateOnMount ? "enter" : "entered" : "unmounted");
	const phaseRef = useRef(phase);
	phaseRef.current = phase;
	const onRestEvent = useEvent(onRest);
	const onPhaseChangeEvent = useEvent(onPhaseChange);
	const onToggleEvent = useEvent(onToggle);
	const flowRef = useRef(0);
	const rafRef = useRef(null);
	const timerRef = useRef(null);
	const cancelRAF = () => {
		if (rafRef.current != null) {
			cancelAnimationFrame(rafRef.current);
			rafRef.current = null;
		}
	};
	const clearTimer = () => {
		if (timerRef.current != null) {
			clearTimeout(timerRef.current);
			timerRef.current = null;
		}
	};
	const nextPaint = (cb) => {
		cancelRAF();
		const flow = flowRef.current;
		rafRef.current = requestAnimationFrame(() => {
			if (flowRef.current !== flow) return;
			rafRef.current = requestAnimationFrame(() => {
				if (flowRef.current !== flow) return;
				rafRef.current = null;
				cb();
			});
		});
	};
	const cleanupEventListeners = () => {
		const element = elementRef.current;
		const listeners = eventListenersRef.current;
		if (element && listeners) {
			element.removeEventListener("transitionstart", listeners.onTransitionStart);
			element.removeEventListener("transitionend", listeners.onTransitionEnd);
			element.removeEventListener("transitioncancel", listeners.onTransitionCancel);
			eventListenersRef.current = null;
		}
	};
	const afterDuration = (cb) => {
		const flow = flowRef.current;
		if (dur === void 0) {
			const element = elementRef.current;
			cleanupEventListeners();
			transitionStartedRef.current = false;
			let completed = false;
			const complete = () => {
				if (completed || flowRef.current !== flow) return;
				completed = true;
				clearTimer();
				cleanupEventListeners();
				cb();
			};
			if (!element) {
				clearTimer();
				timerRef.current = setTimeout(() => {
					if (flowRef.current !== flow) return;
					timerRef.current = null;
					complete();
				}, AUTO_FALLBACK_DURATION);
				return;
			}
			const onTransitionStart = (e) => {
				if (e.target !== element) return;
				if (flowRef.current !== flow) return;
				transitionStartedRef.current = true;
				clearTimer();
			};
			const onTransitionEnd = (e) => {
				if (e.target !== element) return;
				if (flowRef.current !== flow) return;
				complete();
			};
			const onTransitionCancel = (e) => {
				if (e.target !== element) return;
				if (flowRef.current !== flow) return;
				complete();
			};
			eventListenersRef.current = {
				onTransitionStart,
				onTransitionEnd,
				onTransitionCancel
			};
			element.addEventListener("transitionstart", onTransitionStart);
			element.addEventListener("transitionend", onTransitionEnd);
			element.addEventListener("transitioncancel", onTransitionCancel);
			timerRef.current = setTimeout(() => {
				if (flowRef.current !== flow) return;
				if (!transitionStartedRef.current) complete();
			}, 150);
			return;
		}
		if (dur <= 0) {
			cb();
			return;
		}
		clearTimer();
		timerRef.current = setTimeout(() => {
			if (flowRef.current !== flow) return;
			timerRef.current = null;
			cb();
		}, dur);
	};
	const ensureEnterFlow = () => {
		if (phaseRef.current !== "enter") return;
		if (!elementRef.current) return;
		nextPaint(() => {
			if (phaseRef.current !== "enter") return;
			setPhase("entered");
			afterDuration(() => onRestEvent?.("enter"));
		});
	};
	const ensureExitFlow = () => {
		afterDuration(() => {
			setPhase("unmounted");
			onRestEvent?.("exit");
		});
	};
	useEffect(() => {
		++flowRef.current;
		const current = phaseRef.current;
		if (targetShown) if (current === "unmounted" || current === "exit") {
			setPhase("enter");
			ensureEnterFlow();
		} else if (current === "enter") ensureEnterFlow();
		else if (current === "exit-pending") {
			cancelRAF();
			clearTimer();
			setPhase("entered");
		} else {
			cancelRAF();
			clearTimer();
		}
		else if (current === "unmounted") {
			cancelRAF();
			clearTimer();
		} else if (current !== "exit" && current !== "exit-pending") setPhase("exit-pending");
		else if (current === "exit") ensureExitFlow();
		return () => {
			cancelRAF();
			clearTimer();
			cleanupEventListeners();
		};
	}, [
		targetShown,
		dur,
		onRestEvent
	]);
	useLayoutEffect(() => {
		if (phaseRef.current === "enter") ensureEnterFlow();
		else if (phaseRef.current === "exit-pending") nextPaint(() => {
			if (phaseRef.current === "exit-pending") {
				setPhase("exit");
				ensureExitFlow();
			}
		});
		return cancelRAF;
	}, [phase]);
	const reportedPhase = phase === "exit-pending" ? "entered" : phase;
	const prevReportedPhaseRef = useRef(reportedPhase);
	useLayoutEffect(() => {
		if (prevReportedPhaseRef.current !== reportedPhase) {
			prevReportedPhaseRef.current = reportedPhase;
			onPhaseChangeEvent?.(reportedPhase);
		}
	}, [reportedPhase, onPhaseChangeEvent]);
	const isShownNow = phase === "entered" || phase === "exit-pending";
	const prevIsShownRef = useRef(isShownNow);
	useLayoutEffect(() => {
		if (prevIsShownRef.current !== isShownNow) {
			prevIsShownRef.current = isShownNow;
			onToggleEvent?.(isShownNow);
		}
	}, [isShownNow, onToggleEvent]);
	const refCallback = useCallback((node) => {
		if (node) elementRef.current = node;
		else {
			cleanupEventListeners();
			elementRef.current = null;
		}
	}, []);
	if ((phase === "enter" || phase === "entered") && targetShown) storedChildrenRef.current = children;
	const effectiveChildren = preserveContent ? storedChildrenRef.current : children;
	if (phase === "unmounted" && !exposeUnmounted) return null;
	return effectiveChildren({
		phase: reportedPhase === "enter" && duration !== void 0 && !duration ? "entered" : reportedPhase,
		isShown: isShownNow,
		ref: refCallback
	});
}

//#endregion
export { DisplayTransition };
//# sourceMappingURL=DisplayTransition.js.map