/**
 * External dependencies
 */
import deprecated from '@wordpress/deprecated';

/**
 * Internal dependencies
 */
import { registeredBlockComponents } from './registered-block-components-init';

/**
 * Register a Block Component.
 *
 * WooCommerce Blocks allows React Components to be used on the frontend of the store in place of
 * Blocks instead of just serving static content.
 *
 * Registering a Block Component allows you to define which React Component should be used in place
 * of a registered Block. The Component, when rendered, will be passed all Block Attributes.
 *
 * @param {Object}   options           Options to use when registering the block.
 * @param {Function} options.component React component that will be rendered, or the return value from  React.lazy if
 *                                     dynamically imported.
 * @param {string}   options.blockName Name of the block that this component belongs to.
 * @param {string}   [options.context] To make this component available only under a certain context
 *                                     (named parent Block) define it here. If left blank, the
 *                                     Component will be available for all contexts.
 */
export function registerBlockComponent( options ) {
	if ( ! options.context ) {
		options.context = 'any';
	}
	assertOption( options, 'context', 'string' );
	assertOption( options, 'blockName', 'string' );
	assertBlockComponent( options, 'component' );

	const { context, blockName, component } = options;

	if ( ! registeredBlockComponents[ context ] ) {
		registeredBlockComponents[ context ] = {};
	}

	registeredBlockComponents[ context ][ blockName ] = component;
}

/**
 * Asserts that an option is a valid react element or lazy callback. Otherwise, throws an error.
 *
 * @throws Will throw an error if the type of the option doesn't match the expected type.
 * @param {Object} options      Object containing the option to validate.
 * @param {string} optionName   Name of the option to validate.
 */
const assertBlockComponent = ( options, optionName ) => {
	if ( options[ optionName ] ) {
		if ( typeof options[ optionName ] === 'function' ) {
			return;
		}
		if (
			options[ optionName ].$$typeof &&
			options[ optionName ].$$typeof === Symbol.for( 'react.lazy' )
		) {
			return;
		}
	}
	throw new Error(
		`Incorrect value for the ${ optionName } argument when registering a block component. Component must be a valid React Element or Lazy callback.`
	);
};

/**
 * Asserts that an option is of the given type. Otherwise, throws an error.
 *
 * @throws Will throw an error if the type of the option doesn't match the expected type.
 * @param {Object} options      Object containing the option to validate.
 * @param {string} optionName   Name of the option to validate.
 * @param {string} expectedType Type expected for the option.
 */
const assertOption = ( options, optionName, expectedType ) => {
	const actualType = typeof options[ optionName ];
	if ( actualType !== expectedType ) {
		throw new Error(
			`Incorrect value for the ${ optionName } argument when registering a block component. It was a ${ actualType }, but must be a ${ expectedType }.`
		);
	}
};

/**
 * Alias of registerBlockComponent kept for backwards compatibility.
 *
 * @param {Object}   options           Options to use when registering the block.
 * @param {string}   options.main      Name of the parent block.
 * @param {string}   options.blockName Name of the child block being registered.
 * @param {Function} options.component React component used to render the child block.
 */
export function registerInnerBlock( options ) {
	deprecated( 'registerInnerBlock', {
		version: '2.8.0',
		alternative: 'registerBlockComponent',
		plugin: 'WooCommerce Blocks',
		hint: '"main" has been replaced with "context" and is now optional.',
	} );
	assertOption( options, 'main', 'string' );
	registerBlockComponent( {
		...options,
		context: options.main,
	} );
}
