/**
 * External dependencies
 */
import { FormTokenField } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import { getEntitiesInfo } from '@spectra-pro-helpers/loop-builder';

// Query to get all authors.
const AUTHORS_QUERY = {
	who: 'authors',
	per_page: -1,
	_fields: 'id,name',
	context: 'view',
};

/**
 * Renders a token field for selecting authors.
 *
 * This component allows users to select multiple authors using a
 * `FormTokenField`, which provides suggestions based on available authors.
 * It manages the conversion between a comma-separated string of author IDs
 * and the array format required by `FormTokenField`. It also sanitizes
 * the input to ensure only existing authors are processed.
 *
 * @since x.x.x
 *
 * @param {Object} props - The component properties.
 * @return {Element|null} The rendered component or null if no authors are available.
 */
const AuthorControl = ( props ) => {
	const { value, onChange } = props;

	// Get all authors.
	const authorsList = useSelect( ( select ) => {
		const { getUsers } = select( coreStore );
		return getUsers( AUTHORS_QUERY );
	}, [] );

	// If there are no authors, return null.
	if ( ! authorsList ) {
		return null;
	}

	// Get all authors info.
	const authorsInfo = getEntitiesInfo( authorsList );
	/**
	 * We need to normalize the value because the block operates on a
	 * comma(`,`) separated string value and `FormTokenField` needs an
	 * array.
	 */
	const normalizedValue = ! value ? [] : value.toString().split( ',' );
	// Returns only the existing authors ids. This prevents the component
	// from crashing in the editor, when non existing ids are provided.
	const sanitizedValue = normalizedValue.reduce(
		( accumulator, authorId ) => {
			const author = authorsInfo.mapById[ authorId ];

			// Verify that new values point to existing entities.
			if ( author ) {
				accumulator.push( {
					id: authorId,
					value: author.name,
				} );
			}
			return accumulator;
		},
		[]
	);

	// Given an object that maps entity names to entity objects, and a potential entity value,
	const getIdByValue = ( entitiesMappedByName, authorValue ) => {
		const id = authorValue?.id || entitiesMappedByName[ authorValue ]?.id;
		if ( id ) {
			return id;
		}
	};

	/**
	 * Handles the change event of the token field.
	 *
	 * Returns an array of IDs from the newValue array of objects, filtered
	 * to only include existing entities. Calls the provided onChange callback
	 * with the new array of IDs.
	 *
	 * @since x.x.x
	 *
	 * @param {Array} newValue - The new array of entities.
	 */
	const onAuthorChange = ( newValue ) => {
		const ids = Array.from(
			newValue.reduce( ( accumulator, author ) => {
				// Verify that new values point to existing entities.
				const id = getIdByValue( authorsInfo.mapByName, author );
				if ( id ) {
					accumulator.add( id );
				}
				return accumulator;
			}, new Set() )
		);
		onChange( { author: ids.join( ',' ) } );
	};
	return (
		<FormTokenField
			label={ __( 'Authors', 'spectra-pro' ) }
			value={ sanitizedValue }
			suggestions={ authorsInfo.names }
			onChange={ onAuthorChange }
			__experimentalShowHowTo={ false }
			__experimentalExpandOnFocus
			__nextHasNoMarginBottom
			__next40pxDefaultSize
		/>
	);
};

export default AuthorControl;
