/**
 * External dependencies
 */
import clsx from 'clsx';

/**
 * WordPress dependencies
 */
import { useEffect, useRef } from '@wordpress/element';
import { __experimentalUseDragging as useDragging } from '@wordpress/compose';

/**
 * Internal dependencies
 */
import styles from './style.module.scss';

import type { WordPressComponentProps } from '../context';
import type { AngleCircleProps } from './types';

type UseDraggingArgumentType = Parameters< typeof useDragging >[ 0 ];
type UseDraggingCallbackEvent =
	| Parameters< UseDraggingArgumentType[ 'onDragStart' ] >[ 0 ]
	| Parameters< UseDraggingArgumentType[ 'onDragMove' ] >[ 0 ]
	| Parameters< UseDraggingArgumentType[ 'onDragEnd' ] >[ 0 ];

function AngleCircle( {
	value,
	onChange,
	className,
	...props
}: WordPressComponentProps< AngleCircleProps, 'div' > ) {
	const angleCircleRef = useRef< HTMLDivElement >( null );
	const angleCircleCenterRef = useRef< { x: number; y: number } >(
		undefined
	);
	const previousCursorValueRef =
		useRef< CSSStyleDeclaration[ 'cursor' ] >( undefined );

	const setAngleCircleCenter = () => {
		if ( angleCircleRef.current === null ) {
			return;
		}

		const rect = angleCircleRef.current.getBoundingClientRect();
		angleCircleCenterRef.current = {
			x: rect.x + rect.width / 2,
			y: rect.y + rect.height / 2,
		};
	};

	const changeAngleToPosition = ( event: UseDraggingCallbackEvent ) => {
		if ( event === undefined ) {
			return;
		}

		// Prevent (drag) mouse events from selecting and accidentally
		// triggering actions from other elements.
		event.preventDefault();
		// Input control needs to lose focus and by preventDefault above, it doesn't.
		( event.target as HTMLDivElement | null )?.focus();

		if (
			angleCircleCenterRef.current !== undefined &&
			onChange !== undefined
		) {
			const { x: centerX, y: centerY } = angleCircleCenterRef.current;
			onChange(
				getAngle( centerX, centerY, event.clientX, event.clientY )
			);
		}
	};

	const { startDrag, isDragging } = useDragging( {
		onDragStart: ( event ) => {
			setAngleCircleCenter();
			changeAngleToPosition( event );
		},
		onDragMove: changeAngleToPosition,
		onDragEnd: changeAngleToPosition,
	} );

	useEffect( () => {
		if ( isDragging ) {
			if ( previousCursorValueRef.current === undefined ) {
				previousCursorValueRef.current = document.body.style.cursor;
			}
			document.body.style.cursor = 'grabbing';
		} else {
			document.body.style.cursor = previousCursorValueRef.current || '';
			previousCursorValueRef.current = undefined;
		}
	}, [ isDragging ] );

	return (
		// eslint-disable-next-line jsx-a11y/no-static-element-interactions
		<div
			ref={ angleCircleRef }
			onMouseDown={ startDrag }
			className={ clsx(
				'components-angle-picker-control__angle-circle',
				styles[ 'circle-root' ],
				className
			) }
			{ ...props }
		>
			<div
				style={
					value ? { transform: `rotate(${ value }deg)` } : undefined
				}
				className={ clsx(
					'components-angle-picker-control__angle-circle-indicator-wrapper',
					styles[ 'circle-indicator-wrapper' ]
				) }
				tabIndex={ -1 }
			>
				<div
					className={ clsx(
						'components-angle-picker-control__angle-circle-indicator',
						styles[ 'circle-indicator' ]
					) }
				/>
			</div>
		</div>
	);
}

function getAngle(
	centerX: number,
	centerY: number,
	pointX: number,
	pointY: number
) {
	const y = pointY - centerY;
	const x = pointX - centerX;

	const angleInRadians = Math.atan2( y, x );
	const angleInDeg = Math.round( angleInRadians * ( 180 / Math.PI ) ) + 90;
	if ( angleInDeg < 0 ) {
		return 360 + angleInDeg;
	}
	return angleInDeg;
}

export default AngleCircle;
