import { type ReactNode, useLayoutEffect, useRef, useState } from 'react';

import { clsx } from 'clsx';

import { useReducedMotion } from '#/lib/browser/reduced-motion';

import * as styles from '#/components/PostControls/CountWheel.css';

export function CountWheel({
	count,
	isToggled,
	hasBeenToggled,
	renderCount,
}: {
	count: number;
	isToggled: boolean;
	hasBeenToggled: boolean;
	renderCount: (props: { count: number }) => ReactNode;
}) {
	const reducedMotion = useReducedMotion();
	const shouldAnimate = hasBeenToggled && !reducedMotion;

	const prevIsToggled = useRef(isToggled);
	// the outgoing number + its direction during a roll; null at rest. `key` retriggers the CSS animation.
	const [roll, setRoll] = useState<{ from: number; key: number; up: boolean } | null>(null);
	const rollKey = useRef(0);

	// layout effect, not a passive one: the roll must be set up before the browser paints, otherwise
	// the new count flashes at rest for a frame before jumping off-screen to start its enter animation.
	useLayoutEffect(() => {
		if (isToggled === prevIsToggled.current) {
			return;
		}
		prevIsToggled.current = isToggled;
		if (shouldAnimate && decideShouldRoll(isToggled, count)) {
			rollKey.current += 1;
			setRoll({ from: isToggled ? count - 1 : count + 1, key: rollKey.current, up: isToggled });
		}
	}, [isToggled, count, shouldAnimate]);

	if (count < 1) {
		return null;
	}

	return (
		<div className={styles.root}>
			<div
				key={roll?.key}
				className={clsx(styles.current, roll && (roll.up ? styles.enterUp : styles.enterDown))}
				onAnimationEnd={() => setRoll(null)}
			>
				{renderCount({ count })}
			</div>
			{roll ? (
				<div aria-hidden className={roll.up ? styles.exitUp : styles.exitDown}>
					{renderCount({ count: roll.from })}
				</div>
			) : null}
		</div>
	);
}
// roll at direction changes, count boundaries, and while the count is below 1000.
export function decideShouldRoll(isSet: boolean, count: number) {
	let shouldRoll = false;
	if (!isSet && count === 1) {
		shouldRoll = true;
	} else if (count > 1 && count < 1000) {
		shouldRoll = true;
	} else if (count > 0) {
		const mod = count % 100;
		if (isSet && mod === 0) {
			shouldRoll = true;
		} else if (!isSet && mod === 99) {
			shouldRoll = true;
		}
	}
	return shouldRoll;
}
