/**
 * @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md.
 */

import { clamp } from './clamp';
import { prefersReducedMotion } from './prefers-reduced-motion';

/**
 * Creates a tween animation that runs from 0 to 1 over a specified duration.
 * The provided interpolation function is called on each animation frame with the current progress value.
 *
 * @param config - Configuration object
 * @param config.onUpdate - Function called on each frame with current progress (0-1)
 * @param [config.duration=300] - Animation duration in milliseconds
 * @param [config.respectReducedMotion=true] - Whether to respect user's reduced motion preference
 * @returns Animation control object with start and stop methods that return promises
 */
export function tween( { onUpdate, duration = 300, respectReducedMotion = true } ) {
	let animationId = null;
	let startTime = null;
	let resolvePromise = null;
	let rejectPromise = null;

	// Check if reduced motion is preferred
	const shouldSkipAnimation = respectReducedMotion && prefersReducedMotion();

	// Animation frame handler
	const animate = timestamp => {
		// Set start time on first frame
		if ( !startTime ) {
			startTime = timestamp;
		}

		// Calculate progress (0 to 1)
		const elapsed = timestamp - startTime;
		let progress = elapsed / duration;

		// Ensure progress is exactly between 0 and 1
		progress = clamp( 0, 1, progress );

		// Call update function with current progress
		onUpdate( progress );

		// Continue animation if not complete
		if ( progress < 1 ) {
			animationId = requestAnimationFrame( animate );
		} else {
			// Animation complete
			if ( resolvePromise ) {
				resolvePromise();
			}
		}
	};

	// Stop animation
	const stop = () => {
		if ( animationId !== null ) {
			cancelAnimationFrame( animationId );
			animationId = null;

			if ( rejectPromise ) {
				rejectPromise( new Error( 'Animation stopped' ) );
			}
		}
	};

	// Start animation
	const start = () => {
		// Cancel any existing animation
		stop();

		// Reset start time
		startTime = null;

		// Create new promise
		const promise = new Promise( ( resolve, reject ) => {
			resolvePromise = resolve;
			rejectPromise = reject;
		} );

		// If reduced motion is preferred, skip animation and complete immediately
		if ( shouldSkipAnimation ) {
			// Call update function with final value (1.0)
			onUpdate( 1.0 );
			// Resolve the promise immediately
			setTimeout( () => resolvePromise(), 0 );
		} else {
			// Begin normal animation
			animationId = requestAnimationFrame( animate );
		}

		return promise;
	};

	return {
		start,
		stop
	};
}
