/**
 * External dependencies.
 */
import { useSelect } from '@wordpress/data';
import { useEffect, useMemo, useRef, useState } from '@wordpress/element';

/**
 * Internal dependencies.
 */
import parseDynamicContent from '@spectra-pro-extensions/dynamic-content/utils/parseDynamicContent';
import { formatDynamicContent, getPostFieldValue } from '@spectra-pro-extensions/dynamic-content/utils/helpers';

/**
 * Enhances a block edit component with dynamic text content.
 *
 * Processes dynamic content for a block's attribute and updates it in real-time for
 * `current_post` data sources. Uses the WordPress block editor's state to monitor
 * loading and post attributes, ensuring efficient rendering and updates.
 *
 * @since x.x.x
 *
 * @param {Object}   params              - Component parameters.
 * @param {Object}   params.props        - Original props passed to the BlockEdit component.
 * @param {Object}   params.attributes   - Current attributes of the block.
 * @param {string}   params.contentAttr  - Attribute name where dynamic content should be stored.
 * @param {string}   params.currentValue - Current value of the dynamic content attribute.
 * @param {Function} params.BlockEdit    - Original BlockEdit component to be enhanced.
 * @return {Function} Enhanced BlockEdit component with dynamic image support.
 */
const DynamicContentEnhancer = ( { props, attributes, contentAttr, currentValue, BlockEdit } ) => {
	// State to store the processed dynamic content, initialized with the current value.
	const [ processedContent, setProcessedContent ] = useState( currentValue );

	// State to track whether the block editor has loaded.
	const [ isEditorLoaded, setIsEditorLoaded ] = useState( false );

	// Ref to track whether content has been parsed to prevent redundant parsing.
	const hasParsedOnce = useRef( false );

	// Monitor the number of blocks to detect when the editor has loaded.
	const blockCount = useSelect( ( select ) => select( 'core/block-editor' ).getBlockCount(), [] );

	const { context } = props;

	// Update editor loaded state when blocks are detected.
	useEffect( () => {
		if ( blockCount > 0 && ! isEditorLoaded ) {
			setIsEditorLoaded( true );
		}
	}, [ blockCount ] );

	// Parse dynamic content once when the editor loads, if not already parsed.
	useEffect( () => {
		if ( ! isEditorLoaded || hasParsedOnce.current || ! currentValue ) return; // Skip if not loaded or already parsed.

		hasParsedOnce.current = true;

		// Asynchronously parse and update dynamic content if it differs from the current value.
		const parseContent = async () => {
			const newContent = await parseDynamicContent( currentValue, context );
			if ( newContent !== currentValue ) {
				setProcessedContent( newContent );
			}
		};

		parseContent();
	}, [ isEditorLoaded, context?.postId, context?.postType ] );

	// Check if the content includes `current_post` dynamic content.
	const hasCurrentPost = currentValue.includes(
		'data-spectra-dc="{&quot;source&quot;:{&quot;type&quot;:&quot;current_post&quot;,'
	);

	// Fetch real-time post attributes from the editor for `current_post` data source.
	const postAttributes = useSelect(
		( select ) => {
			if ( ! hasCurrentPost ) return null;

			const editorStore = select( 'core/editor' );

			// Construct a post object with relevant attributes for dynamic content.
			const post = {
				id: editorStore.getCurrentPostId(),
				title: { rendered: editorStore.getEditedPostAttribute( 'title' ) || '' },
				excerpt: { rendered: editorStore.getEditedPostAttribute( 'excerpt' ) || '' },
				date_gmt: editorStore.getEditedPostAttribute( 'date_gmt' ) || '',
				modified_gmt: editorStore.getEditedPostAttribute( 'modified_gmt' ) || '',
				featured_media: editorStore.getEditedPostAttribute( 'featured_media' ) || 0,
			};

			return post;
		},
		[ hasCurrentPost ]
	);

	// Update dynamic content in real-time for `current_post` data source.
	useEffect( () => {
		// Skip if initial parsing hasn't occurred, editor isn't loaded, or no current_post content.
		if ( ! hasParsedOnce.current || ! isEditorLoaded || ! hasCurrentPost || ! currentValue ) {
			return;
		}

		// Process dynamic content elements and update if changes are detected.
		const updateContent = async () => {
			// Parse the current content into a DOM structure.
			const doc = new DOMParser().parseFromString( currentValue, 'text/html' );
			let hasChanges = false;

			// Select all dynamic content elements.
			const elements = Array.from( doc.querySelectorAll( 'span.spectra-pro-dc-content[data-spectra-dc]' ) );
			for ( const element of elements ) {
				const settings = JSON.parse( element.getAttribute( 'data-spectra-dc' ) || '{}' );
				if ( settings?.source?.type !== 'current_post' || ! settings?.source?.field ) continue;

				const sourceField = settings.source.field;

				// Limit real-time updates to supported fields.
				const validFields = [ 'post_title', 'post_excerpt', 'post_date', 'post_time', 'featured_image' ];
				if ( ! validFields.includes( sourceField ) ) continue;

				// Fetch and format the dynamic content for the element.
				const content = await getPostFieldValue( settings, postAttributes );
				const formattedContent = await formatDynamicContent( content, settings );

				// Update element content if it has changed.
				if ( element.innerHTML !== formattedContent ) {
					element.innerHTML = formattedContent;
					hasChanges = true;
				}
			}

			// Update state if any changes were made.
			if ( hasChanges ) {
				setProcessedContent( doc.body.innerHTML );
			}
		};

		updateContent();
	}, [
		isEditorLoaded,
		hasCurrentPost,
		currentValue,
		postAttributes?.title?.rendered,
		postAttributes?.excerpt?.rendered,
		postAttributes?.date_gmt,
		postAttributes?.modified_gmt,
		postAttributes?.featured_media,
	] );

	// Memoize enhanced props to optimize rendering by updating only when necessary.
	const enhancedProps = useMemo(
		() => ( {
			...props,
			attributes: {
				...attributes,
				[ contentAttr ]: processedContent,
			},
		} ),
		[ props, processedContent ]
	);

	// Render the original BlockEdit component with enhanced props.
	return <BlockEdit { ...enhancedProps } />;
};

export default DynamicContentEnhancer;
