import { React, ReactDND, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses, sprintf } from '@gravityforms/utils';
import { UP, DOWN, BLOCK, INLINE } from './constants';
import { ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, TAB } from '../../utils/keymap';
import itemTypes from './item-types';
import Button from '../../elements/Button';
import Text from '../../elements/Text';

const { useRef, useState } = React;
const { useDrag, useDrop } = ReactDND;

const RepeaterItem = ( {
	blockContentTitle = '',
	blockHeaderAttributes = {},
	blockHeaderClasses = [],
	children = null,
	collapseItem = () => {},
	collapsible = false,
	collapsibleButtonAttributes = {},
	collapsibleButtonClasses = [],
	deleteButtonAttributes = {},
	deleteButtonClasses = [],
	deleteItem = () => {},
	downButtonAttributes = {},
	downButtonClasses = [],
	dragHandleAttributes = {},
	dragHandleClasses = [],
	fillContent = false,
	i18n = {},
	id = '',
	index = 0,
	isCollapsed = false,
	isDraggable = false,
	isSortable = false,
	itemAttributes = {},
	itemClasses = [],
	itemCount = 0,
	itemDraggable = false,
	itemSpacing = '',
	minItems = 0,
	moveItem = () => {},
	showArrows = false,
	showDragHandle = false,
	showDelete = false,
	speak = () => {},
	type = INLINE,
	upButtonAttributes = {},
	upButtonClasses = [],
} ) => {
	const ref = useRef( null );
	const dragHandleRef = useRef( null );
	const startIndexRef = useRef( null );
	const [ keyboardDragActive, setKeyboardDragActive ] = useState( false );

	const handleKeyboardNav = ( event ) => {
		event.stopPropagation();

		if ( event.key === TAB && keyboardDragActive ) {
			event.preventDefault();
			return;
		}

		if ( ! keyboardDragActive ) {
			return;
		}

		if ( ( event.key === ARROW_UP || event.key === ARROW_LEFT ) ) {
			moveItem( index, index - 1, id );
			return;
		}

		if ( ( event.key === ARROW_DOWN || event.key === ARROW_RIGHT ) ) {
			moveItem( index, index + 1, id );
		}
	};

	const beginDragging = () => {
		speak( sprintf( i18n.beginDrag, id ) );
		setKeyboardDragActive( true );
	};

	const endDragging = () => {
		speak( sprintf( i18n.endDrag, id ) );
		setKeyboardDragActive( false );
	};

	const toggleDragging = () => {
		if ( keyboardDragActive ) {
			endDragging();
		} else {
			beginDragging();
		}
	};

	const handleCollapsibleClick = () => {
		collapseItem( index, id );
	};

	const handleArrowPress = ( event, direction ) => {
		const toIndex = direction === UP ? index - 1 : index + 1;
		moveItem( index, toIndex, id );
	};

	const [ { isDragging }, drag, preview ] = useDrag( {
		type: itemTypes.REPEATER_ITEM,
		item: { id, index },
		collect: ( monitor ) => ( {
			isDragging: monitor.isDragging(),
		} ),
	} );

	const [ { handlerId }, drop ] = useDrop( {
		accept: itemTypes.REPEATER_ITEM,
		collect( monitor ) {
			return {
				handlerId: monitor.getHandlerId(),
				isOver: monitor.isOver(),
			};
		},
		hover( item, monitor ) {
			if ( ! ref.current || item.index === index ) {
				return;
			}

			const dragIndex = item.index;
			const hoverIndex = index;

			if ( startIndexRef.current === null ) {
				startIndexRef.current = dragIndex;
			}

			if ( dragIndex === hoverIndex ) {
				return;
			}

			const hoverBoundingRect = ref.current?.getBoundingClientRect();
			const hoverMiddleY = ( hoverBoundingRect.bottom - hoverBoundingRect.top ) / 2;
			const clientOffset = monitor.getClientOffset();
			const hoverClientY = clientOffset.y - hoverBoundingRect.top;

			if ( dragIndex < hoverIndex && hoverClientY < hoverMiddleY ) {
				return;
			}

			if ( dragIndex > hoverIndex && hoverClientY > hoverMiddleY ) {
				return;
			}

			moveItem( dragIndex, hoverIndex, id );
			item.index = hoverIndex;
		},
		drop( item ) {
			speak( sprintf( i18n.endDrop, id, item.index ) );
		},
	} );

	if ( itemDraggable && isDraggable && isSortable ) {
		drag( drop( ref ) );
	} else if ( showDragHandle && isDraggable && isSortable ) {
		drag( dragHandleRef );
		preview( drop( ref ) );
	}

	const renderControls = () => {
		if ( ( ! showArrows && ( ! showDragHandle || ! isDraggable ) ) || ! isSortable ) {
			return null;
		}

		const dragHandleProps = {
			size: 'size-height-m',
			type: 'icon-white',
			icon: 'drag-indicator',
			label: i18n.dragLabel,
			customAttributes: {
				onKeyDown: handleKeyboardNav,
			},
			onClick: ( e ) => toggleDragging( e ),
			customClasses: classnames( [
				'gform-repeater-item__control',
				'gform-repeater-item__control--drag-toggle',
			], dragHandleClasses ),
			ref: showDragHandle && ! itemDraggable ? dragHandleRef : undefined,
			disabled: itemCount === 1,
			...dragHandleAttributes,
		};

		const upButtonProps = {
			size: 'size-height-m',
			type: 'icon-white',
			label: sprintf( i18n.upLabel, id ),
			iconPrefix: 'gform-common-icon',
			icon: 'chevron-up',
			onClick: ( e ) => handleArrowPress( e, UP ),
			customClasses: classnames( [
				'gform-repeater-item__control',
				'gform-repeater-item__control--up',
			], upButtonClasses ),
			disabled: itemCount === 1 || ( itemCount !== 1 && index === 0 ),
			...upButtonAttributes,
		};

		const downButtonProps = {
			size: 'size-height-m',
			type: 'icon-white',
			label: sprintf( i18n.downLabel, id ),
			iconPrefix: 'gform-common-icon',
			icon: 'chevron-down',
			onClick: ( e ) => handleArrowPress( e, DOWN ),
			customClasses: classnames( [
				'gform-repeater-item__control',
				'gform-repeater-item__control--down',
			], downButtonClasses ),
			disabled: itemCount === 1 || ( itemCount !== 1 && index === itemCount - 1 ),
			...downButtonAttributes,
		};

		const className = classnames( [
			'gform-repeater-item__controls',
		] );

		return (
			<div className={ className }>
				{ showDragHandle && isDraggable && isSortable && <Button { ...dragHandleProps } /> }
				{ showArrows && isSortable && <Button { ...upButtonProps } /> }
				{ showArrows && isSortable && <Button { ...downButtonProps } /> }
			</div>
		);
	};

	const itemWrapperProps = {
		className: 'gform-repeater-item__wrapper',
	};

	const blockHeaderProps = {
		className: classnames( [
			'gform-repeater-item__block-header',
		], blockHeaderClasses ),
		size: 'text-sm',
		weight: 'medium',
		...blockHeaderAttributes,
	};

	const deleteButtonProps = {
		size: 'size-height-m',
		type: type === INLINE ? 'icon-white' : 'white',
		icon: 'trash',
		iconPosition: 'leading',
		label: i18n.deleteLabel,
		onClick: () => deleteItem( index, id ),
		disabled: minItems && itemCount <= minItems,
		customClasses: classnames( [
			'gform-repeater-item__delete',
		], deleteButtonClasses ),
		...deleteButtonAttributes,
	};

	const collapsibleButtonProps = {
		size: 'size-height-m',
		type: 'unstyled',
		iconPrefix: 'gform-common-icon',
		icon: 'chevron-down',
		onClick: ( e ) => handleCollapsibleClick( e ),
		customClasses: classnames( [
			'gform-repeater-item__collapsible',
		], collapsibleButtonClasses ),
		...collapsibleButtonAttributes,
	};

	const componentProps = {
		className: classnames( {
			'gform-repeater-item': true,
			[ `gform-repeater-item--type-${ type }` ]: type,
			'gform-repeater-item--has-arrows': showArrows && isSortable,
			'gform-repeater-item--has-drag-handle': showDragHandle && isDraggable && isSortable,
			'gform-repeater-item--is-draggable': isDraggable && isSortable,
			'gform-repeater-item--is-sortable': isSortable,
			'gform-repeater-item--is-collapsed': isCollapsed,
			'gform-repeater-item--is-dragging': isDragging,
			'gform-repeater-item--fill-content': fillContent,
			'gform-repeater-item--disable-item-drag': ! itemDraggable,
			'gform-repeater-item--is-keyboard-nav': keyboardDragActive,
			...spacerClasses( itemSpacing ),
		}, itemClasses ),
		'data-index': index,
		'data-handler-id': handlerId,
		id,
		...itemAttributes,
	};

	return (
		<div { ...componentProps } ref={ ref }>
			<div { ...itemWrapperProps }>
				{ renderControls() }
				{ type === BLOCK && blockContentTitle && (
					<Text { ...blockHeaderProps }>{ blockContentTitle }</Text>
				) }
				{ type === INLINE && children }
				{ showDelete && type === INLINE && <Button { ...deleteButtonProps } /> }
				{ collapsible && type === BLOCK && <Button { ...collapsibleButtonProps } /> }
			</div>
			{ type === BLOCK && (
				<div className="gform-repeater-item__block-content">
					{ children }
					{ showDelete && <Button { ...deleteButtonProps } /> }
				</div>
			) }
		</div>
	);
};

RepeaterItem.propTypes = {
	blockContentTitle: PropTypes.string,
	blockHeaderAttributes: PropTypes.object,
	blockHeaderClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	children: PropTypes.node,
	collapseItem: PropTypes.func,
	collapsible: PropTypes.bool,
	collapsibleButtonAttributes: PropTypes.object,
	collapsibleButtonClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	deleteButtonAttributes: PropTypes.object,
	deleteButtonClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	deleteItem: PropTypes.func,
	downButtonAttributes: PropTypes.object,
	downButtonClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	dragHandleAttributes: PropTypes.object,
	dragHandleClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	fillContent: PropTypes.bool,
	i18n: PropTypes.object,
	id: PropTypes.string.isRequired,
	index: PropTypes.number.isRequired,
	isCollapsed: PropTypes.bool,
	isDraggable: PropTypes.bool,
	isSortable: PropTypes.bool,
	itemAttributes: PropTypes.object,
	itemClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
	itemCount: PropTypes.number,
	itemDraggable: PropTypes.bool,
	itemSpacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	minItems: PropTypes.number,
	moveItem: PropTypes.func.isRequired,
	showArrows: PropTypes.bool,
	showDragHandle: PropTypes.bool,
	showDelete: PropTypes.bool,
	speak: PropTypes.func,
	type: PropTypes.string,
	upButtonAttributes: PropTypes.object,
	upButtonClasses: PropTypes.oneOfType( [
		PropTypes.element,
		PropTypes.func,
		PropTypes.array,
		PropTypes.string,
	] ),
};

export default RepeaterItem;
