/**
 * WordPress dependencies
 */
import { renderToString } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';

/**
 * Internal dependencies
 */
import * as node from './node';

/**
 * A representation of a block's rich text value.
 */
export type BlockChildren = Array< string | Record< string, unknown > >;

/**
 * Given block children, returns a serialize-capable WordPress element.
 *
 * @param children Block children object to convert.
 *
 * @return A serialize-capable element.
 */
export function getSerializeCapableElement(
	children: BlockChildren
): BlockChildren {
	// The fact that block children are compatible with the element serializer is
	// merely an implementation detail that currently serves to be true, but
	// should not be mistaken as being a guarantee on the external API. The
	// public API only offers guarantees to work with strings (toHTML) and DOM
	// elements (fromDOM), and should provide utilities to manipulate the value
	// rather than expect consumers to inspect or construct its shape (concat).
	return children;
}

/**
 * Given block children, returns an array of block nodes.
 *
 * @param children Block children object to convert.
 *
 * @return An array of individual block nodes.
 */
function getChildrenArray( children: BlockChildren ): BlockChildren {
	deprecated( 'wp.blocks.children.getChildrenArray', {
		since: '6.1',
		version: '6.3',
		link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
	} );

	// The fact that block children are compatible with the element serializer
	// is merely an implementation detail that currently serves to be true, but
	// should not be mistaken as being a guarantee on the external API.
	return children;
}

/**
 * Given two or more block nodes, returns a new block node representing a
 * concatenation of its values.
 *
 * @param blockNodes Block nodes to concatenate.
 *
 * @return Concatenated block node.
 */
export function concat(
	...blockNodes: Array< BlockChildren | string >
): BlockChildren {
	deprecated( 'wp.blocks.children.concat', {
		since: '6.1',
		version: '6.3',
		alternative: 'wp.richText.concat',
		link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
	} );

	const result: BlockChildren = [];
	for ( let i = 0; i < blockNodes.length; i++ ) {
		const blockNode = Array.isArray( blockNodes[ i ] )
			? blockNodes[ i ]
			: [ blockNodes[ i ] ];
		for ( let j = 0; j < blockNode.length; j++ ) {
			const child = blockNode[ j ] as string | Record< string, unknown >;
			const canConcatToPreviousString =
				typeof child === 'string' &&
				typeof result[ result.length - 1 ] === 'string';

			if ( canConcatToPreviousString ) {
				result[ result.length - 1 ] += child;
			} else {
				result.push( child );
			}
		}
	}

	return result;
}

/**
 * Given an iterable set of DOM nodes, returns equivalent block children.
 * Ignores any non-element/text nodes included in set.
 *
 * @param domNodes Iterable set of DOM nodes to convert.
 *
 * @return Block children equivalent to DOM nodes.
 */
export function fromDOM(
	domNodes: NodeListOf< ChildNode > | NodeList
): BlockChildren {
	deprecated( 'wp.blocks.children.fromDOM', {
		since: '6.1',
		version: '6.3',
		alternative: 'wp.richText.create',
		link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
	} );

	const result: BlockChildren = [];
	for ( let i = 0; i < domNodes.length; i++ ) {
		try {
			result.push( node.fromDOM( domNodes[ i ] ) );
		} catch {
			// Simply ignore if DOM node could not be converted.
		}
	}

	return result;
}

/**
 * Given a block node, returns its HTML string representation.
 *
 * @param children Block node(s) to convert to string.
 *
 * @return String HTML representation of block node.
 */
export function toHTML( children: BlockChildren ): string {
	deprecated( 'wp.blocks.children.toHTML', {
		since: '6.1',
		version: '6.3',
		alternative: 'wp.richText.toHTMLString',
		link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
	} );

	const element = getSerializeCapableElement( children );

	return renderToString( element as React.ReactNode );
}

/**
 * Given a selector, returns an hpq matcher generating a BlockChildren value
 * matching the selector result.
 *
 * @param selector DOM selector.
 *
 * @return hpq matcher.
 */
export function matcher(
	selector?: string
): ( domNode: Element ) => BlockChildren {
	deprecated( 'wp.blocks.children.matcher', {
		since: '6.1',
		version: '6.3',
		alternative: 'html source',
		link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
	} );

	return ( domNode: Element ): BlockChildren => {
		let match: Element | null = domNode;

		if ( selector ) {
			match = domNode.querySelector( selector );
		}

		if ( match ) {
			return fromDOM( match.childNodes );
		}

		return [];
	};
}

/**
 * Object of utility functions used in managing block attribute values of
 * source `children`.
 *
 * @see https://github.com/WordPress/gutenberg/pull/10439
 *
 * @deprecated since 4.0. The `children` source should not be used, and can be
 *             replaced by the `html` source.
 *
 * @private
 */
export default {
	concat,
	getChildrenArray,
	fromDOM,
	toHTML,
	matcher,
};
