import { React, PropTypes, SimpleBar, classnames } from '@gravityforms/libraries';
import { ConditionalWrapper, useIdContext, useStoreContext } from '@gravityforms/react-utils';
import {
	getId,
	getComponent,
} from './utils';
import RingLoader from '../Loaders/RingLoader';

const { forwardRef } = React;

/**
 * @module DropdownList
 * @description The list component for the dropdown.
 *
 * @since 4.5.0
 *
 * @param {object}              props                      The component props.
 * @param {boolean}             props.ajaxSearch           Whether to use ajax search for the dropdown.
 * @param {Function}            props.handleBlur           The blur event handler.
 * @param {Function}            props.handleKeyDownCapture The key down capture event handler.
 * @param {Function}            props.handleListKeyDown    The list key down event handler.
 * @param {boolean}             props.hasSearch            Whether the dropdown has search.
 * @param {string}              props.label                The label of the dropdown.
 * @param {Array}               props.listAttributes       Custom attributes for the list.
 * @param {string|Array|object} props.listClasses          Custom classes for the list.
 * @param {boolean}             props.multi                Whether the dropdown is multi-select.
 * @param {number}              props.popoverMaxHeight     The popover max height.
 * @param {boolean}             props.searchIsLoading      Whether the dropdown list items are loading from search or not.
 * @param {string}              props.selectedIcon         The selected icon.
 * @param {string}              props.selectedIconPrefix   The selected icon prefix.
 * @param {Function}            props.selectItem           The select item event handler.
 * @param {boolean}             props.simplebar            Whether to use simplebar for the dropdown.
 * @param {object}              ref                        The ref object.
 *
 * @return {JSX.Element} The list component.
 */
const DropdownList = forwardRef( ( {
	ajaxSearch = false,
	handleBlur = () => {},
	handleKeyDownCapture = () => {},
	handleListKeyDown = () => {},
	hasSearch = false,
	label = '',
	listAttributes = {},
	listClasses = [],
	multi = false,
	popoverMaxHeight = 0,
	searchIsLoading = false,
	selectedIcon = 'check-mark-alt',
	selectedIconPrefix = 'gravity-component-icon',
	selectItem = () => {},
	simplebar = true,
}, ref ) => {
	const id = useIdContext();
	const activeItem = useStoreContext( ( state ) => state.activeItem );
	const listItems = useStoreContext( ( state ) => state.listItems );
	const selectedItem = useStoreContext( ( state ) => state.selectedItem );
	const setActiveItem = useStoreContext( ( state ) => state.setActiveItem );
	const baseElRef = useStoreContext( ( state ) => state.baseElRef );

	/**
	 * @function getListItems
	 * @description Gets the list items for the dropdown.
	 *
	 * @since 4.5.0
	 *
	 * @param {Array} items The list items.
	 *
	 * @return {Array} An array of list items.
	 */
	const getListItems = ( items ) => {
		return items.map( ( item ) => {
			// Return null if search.
			if ( item.type === 'search' ) {
				return null;
			}

			// Recursively get group items.
			if ( item.type === 'group' ) {
				const hasLabel = item.items.some( ( subItem ) => subItem.type === 'groupLabel' );
				const groupProps = {
					className: classnames( {
						'gform-dropdown__list-group': true,
						'gform-dropdown__list-group--has-label': hasLabel,
					} ),
					id: item.id,
					role: 'group',
				};
				return (
					<div { ...groupProps } key={ item.id }>
						{ getListItems( item.items ) }
					</div>
				);
			}

			// Get the group label.
			if ( item.type === 'groupLabel' ) {
				const groupLabelProps = {
					className: classnames( [
						'gform-dropdown__list-group-label',
						'gform-text--color-comet',
						'gform-typography--size-text-xs',
						'gform-typography--weight-regular',
					] ),
					id: item.id,
					role: 'presentation',
				};
				return (
					<div { ...groupLabelProps } key={ item.id }>
						{ item.label }
					</div>
				);
			}

			// Get the list item.
			let selected = selectedItem.value === item.value;
			if ( multi && Array.isArray( selectedItem ) ) {
				selected = selectedItem.some( ( selItem ) => selItem.value === item.value );
			}
			const isDisabled = ! ! item.disabled;
			const itemProps = {
				className: classnames( 'gform-dropdown__list-item', {
					'gform-dropdown__list-item--disabled': isDisabled,
				} ),
				id: item.id,
				'aria-selected': selected ? 'true' : 'false',
				'aria-disabled': isDisabled ? 'true' : 'false',
				tabIndex: '-1',
				role: 'option',
				onMouseMove: isDisabled ? null : () => {
					setActiveItem( item );
				},
				onClick: isDisabled ? null : ( event ) => selectItem( event, item ),
				onFocus: () => baseElRef?.current?.focus(),
			};
			if ( ! isDisabled && activeItem.value === item.value ) {
				itemProps[ 'data-active-item' ] = 'true';
			}
			const itemInnerProps = {
				className: classnames( [
					'gform-dropdown__list-item-inner',
					'gform-text',
					'gform-text--color-port',
					'gform-typography--size-text-sm',
					'gform-typography--weight-regular',
				] ),
			};

			return (
				<div { ...itemProps } key={ item.id }>
					<div { ...itemInnerProps }>
						{ item.beforeLabel && (
							<span className="gform-dropdown__list-item-before-label">
								{ getComponent( item.beforeLabel ) }
							</span>
						) }
						<span className="gform-dropdown__list-item-label">{ item.label }</span>
						{ ( ( item.afterLabel && ! multi ) || ( multi && selected ) ) && (
							<span className="gform-dropdown__list-item-after-label">
								{ multi && selected
									? getComponent( {
										component: 'Icon',
										props: {
											iconPrefix: selectedIconPrefix,
											icon: selectedIcon,
										},
									} )
									: getComponent( item.afterLabel )
								}
							</span>
						) }
					</div>
				</div>
			);
		} );
	};

	const listId = getId( id, 'list' );

	const listProps = {
		className: classnames( [ 'gform-dropdown__list' ], listClasses ),
		...listAttributes,
		id: listId,
		onBlur: handleBlur,
		onKeyDown: ( event ) => {
			handleListKeyDown( event );
		},
		onKeyDownCapture: ( event ) => handleKeyDownCapture( event ),
		role: 'listbox',
	};
	const simpleBarProps = {
		className: 'gform-dropdown__list-simplebar',
	};

	if ( ! hasSearch ) {
		listProps.tabIndex = '0';
		if ( label ) {
			const labelId = getId( id, 'label' );
			listProps[ 'aria-labelledby' ] = labelId;
		}
		if ( listItems.flatItems.length ) {
			listProps[ 'aria-activedescendant' ] = activeItem.id;
		}
	}
	if ( popoverMaxHeight ) {
		const listMaxHeight = hasSearch ? Math.max( popoverMaxHeight - 58, 0 ) : popoverMaxHeight;

		if ( listMaxHeight > 0 ) {
			const listMaxHeightPx = `${ listMaxHeight }px`;
			listProps.style = {
				maxHeight: listMaxHeightPx,
			};
			simpleBarProps.style = {
				height: listMaxHeightPx,
			};
		}
	}

	return (
		<div { ...listProps } ref={ ref }>
			<ConditionalWrapper
				condition={ simplebar && popoverMaxHeight > 0 }
				wrapper={ ( ch ) => (
					<div { ...simpleBarProps } >
						<SimpleBar>
							{ ch }
						</SimpleBar>
					</div>
				) }
			>
				{ getListItems( listItems.items ) }
			</ConditionalWrapper>
			{ ajaxSearch && searchIsLoading && (
				<div className="gform-dropdown__list-loader-wrapper">
					<RingLoader customClasses={ [ 'gform-dropdown__list-loader' ] } foreground="#242748" />
				</div>
			) }
		</div>
	);
} );

DropdownList.propTypes = {
	ajaxSearch: PropTypes.bool,
	handleBlur: PropTypes.func,
	handleKeyDownCapture: PropTypes.func,
	handleListKeyDown: PropTypes.func,
	hasSearch: PropTypes.bool,
	label: PropTypes.string,
	listAttributes: PropTypes.object,
	listClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	multi: PropTypes.bool,
	popoverMaxHeight: PropTypes.number,
	searchIsLoading: PropTypes.bool,
	selectedIcon: PropTypes.string,
	selectedIconPrefix: PropTypes.string,
	selectItem: PropTypes.func,
	simplebar: PropTypes.bool,
};

export default DropdownList;
