/**
 * External dependencies.
 */
import { Notice, Button, __experimentalHStack as HStack } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';

/**
 * Internal dependencies.
 */
import { useDynamicContent } from '@spectra-pro-extensions/dynamic-content/context/DynamicContentContext';
import DataSourceField from '@spectra-pro-extensions/dynamic-content/components/ImageFields/DataSourceField';
import SourceField from '@spectra-pro-extensions/dynamic-content/components/ImageFields/SourceField';
import { isEmpty } from '@spectra-pro-helpers';
import { getDynamicContent } from '@spectra-pro-extensions/dynamic-content/utils/helpers';
import FallbackImage from '@spectra-pro-extensions/dynamic-content/components/ImageFields/FallbackImage';
import { useDispatch } from '@wordpress/data';

/**
 * Renders a panel for configuring dynamic image content settings.
 *
 * Provides an interface to select data sources, fields, and fallback images for dynamic
 * image content. Updates block attributes with the selected configuration and applies
 * dynamic image data to specific blocks (e.g., core/image, Slider, Container).
 *
 * @since x.x.x
 *
 * @param {Object}   props                                   - The component props.
 * @param {string}   [props.type='text']                     - The content type ('text' or 'image').
 * @param {string}   [props.blockName=null]                  - The name of the block (e.g., 'core/image').
 * @param {string}   [props.attributeLabel=null]             - The attribute key for media content.
 * @param {Object}   [props.attributeValue=null]             - The current value of the media attribute.
 * @param {Object}   props.attributes                        - The block's attributes.
 * @param {Object}   props.attributes.spectraProDynamicImage - The dynamic image settings.
 * @param {Function} props.setAttributes                     - Function to update block attributes.
 * @return {Element} The dynamic image content configuration panel.
 */
const DynamicImage = ( props ) => {
	const {
		type = 'text',
		blockName = null,
		attributeLabel = null,
		attributeValue = null,
		attributes: { spectraProDynamicImage, url = '' },
		setAttributes,
		clientId,
		context = {},
	} = props;

	// Access dynamic content context for settings and updates.
	const { settings, updateSetting, updateAdvancedSetting } = useDynamicContent();

	// State for error messages and loading status.
	const [ error, setError ] = useState( null );
	const [ isLoading, setIsLoading ] = useState( false );

	const { __unstableMarkNextChangeAsNotPersistent, updateBlockAttributes } = useDispatch( 'core/block-editor' );

	/**
	 * Validates the current settings for dynamic image content.
	 *
	 * Ensures required fields are present based on the selected data source.
	 *
	 * @return {boolean} True if settings are valid, false otherwise.
	 */
	const isValid = () => {
		const { source } = settings;

		// Base requirement: type and field must exist.
		if ( ! source.type || ! source.field ) {
			return false;
		}

		switch ( source.type ) {
			case 'post_type':
				// Must have postId.
				return !! source.postId;
			case 'archive':
				// If field is 'archive_meta', must have archiveMetaKey.
				if ( source.field === 'archive_meta' ) {
					return !! source.archiveMetaKey;
				}
				return true;
			default:
				return true;
		}
	};

	// Sync existing dynamic image settings with context when they change.
	useEffect( () => {
		if ( isEmpty( spectraProDynamicImage ) ) return;

		// Update context with each key-value pair from dynamic image settings.
		Object.entries( spectraProDynamicImage ).forEach( ( [ key, val ] ) => {
			if ( key === 'advanced' ) {
				// Handle advanced settings separately.
				Object.entries( val ).forEach( ( [ advKey, advVal ] ) => {
					updateAdvancedSetting( advKey, advVal );
				} );
			} else if ( key === 'source' ) {
				// Handle nested source settings.
				Object.entries( val ).forEach( ( [ sourceKey, sourceVal ] ) => {
					updateSetting( `source.${ sourceKey }`, sourceVal );
				} );
			} else {
				updateSetting( key, val );
			}
		} );
	}, [ spectraProDynamicImage ] );

	/**
	 * Applies dynamic image settings to block attributes.
	 *
	 * Constructs a formatted data object, fetches dynamic image content, and updates
	 * block attributes for the specified block type.
	 */
	const handleApply = async () => {
		if ( ! isValid() ) return;
		
		setIsLoading( true );
		setError( null );

		try {
			// Initialize formatData with required source properties.
			const formatData = {
				type: 'image',
				source: {
					type: settings.source.type,
					field: settings.source.field,
				},
			};

			// Utility to set non-empty values in formatData.
			const setIfExists = ( target, key, val ) => {
				if ( val !== undefined && val !== '' ) target[ key ] = val;
			};

			// Handlers for source-specific settings.
			const sourceHandlers = {
				post_type: () => {
					setIfExists( formatData.source, 'postId', settings.source.postId );
					setIfExists( formatData.source, 'postType', settings.source.postType );
				},
				archive: () => {
					if ( settings.source.field === 'archive_meta' ) {
						setIfExists( formatData.source, 'archiveMetaKey', settings.source.archiveMetaKey );
					}
				},
			};

			// Handlers for field-specific settings.
			const fieldHandlers = {
				custom_field: () => {
					setIfExists( formatData.source, 'postCustomField', settings.source.postCustomField );
					if ( settings.source.postCustomField === 'custom' ) {
						setIfExists( formatData.source, 'postMetaKey', settings.source.postMetaKey );
					}
				},
				author_info: () => {
					setIfExists( formatData.source, 'authorField', settings.source.authorField );
					if ( settings.source.authorField === 'custom' ) {
						setIfExists( formatData.source, 'authorMetaKey', settings.source.authorMetaKey );
					}
				},
			};

			// Apply source and field-specific settings.
			sourceHandlers[ settings.source.type ]?.();
			fieldHandlers[ settings.source.field ]?.();

			// Include fallback image from advanced settings if set.
			if ( settings.advanced?.fallback ) {
				formatData.advanced = { fallback: settings.advanced.fallback };
			}

			// If current post, update postId based on context.
			if ( 'current_post' === settings.source.type && context?.postType && context?.postId ) {
				updateSetting( 'source.postId', context.postId );
				updateSetting( 'source.postType', context.postType );
			}

			setAttributes( { spectraProDynamicImage: formatData } );

			// Fetch dynamic image content.
			const dynamicImage = await getDynamicContent( settings, 'image' );
			
			// Ensure minimum loading time for better UX feedback.
			await new Promise( resolve => setTimeout( resolve, 300 ) );

			// Check if dynamicImage has a valid value.
			// getDynamicContent returns either an object with url property or empty string.
			const hasValidImage = dynamicImage && typeof dynamicImage === 'object' && dynamicImage.url;

			// Update attributes for Slider and Container blocks.
			if ( attributeLabel && attributeValue ) {
				/**
				 * Use requestAnimationFrame to defer the attribute update until the next browser paint cycle.
				 * This ensures the UI is updated smoothly and prevents potential rendering conflicts.
				 *
				 * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
				 */
				requestAnimationFrame( () => {
					/**
					 * Mark the next change as not persistent to prevent it from being saved to the block's undo history.
					 * This is important for dynamic content as these changes are visual updates in the editor
					 * rather than actual content edits that should be tracked in the undo stack.
					 */
					__unstableMarkNextChangeAsNotPersistent();
					updateBlockAttributes( clientId, {
						[ attributeLabel ]: {
							...attributeValue,
							media: hasValidImage ? dynamicImage : undefined,
						},
					} );
				} );
			}

			// Update attributes for core/image block.
			if ( blockName && 'core/image' === blockName ) {
				/**
				 * Use requestAnimationFrame to defer the attribute update until the next browser paint cycle.
				 * This ensures the UI is updated smoothly and prevents potential rendering conflicts.
				 *
				 * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
				 */
				requestAnimationFrame( () => {
					if ( url?.length > 0 ) {
						/**
						 * Mark the next change as not persistent to prevent it from being saved to the block's undo history.
						 * This is important for dynamic content as these changes are visual updates in the editor
						 * rather than actual content edits that should be tracked in the undo stack.
						 */
						__unstableMarkNextChangeAsNotPersistent();
						updateBlockAttributes( clientId, {
							url: dynamicImage?.url,
							id: dynamicImage?.id,
						} );
					} else {
						setAttributes( { url: dynamicImage?.url, id: dynamicImage?.id } );
					}
				} );
			}
		} catch ( e ) {
			setError( __( 'Failed to apply dynamic image.', 'spectra-pro' ) );
		} finally {
			setIsLoading( false );
		}
	};

	/**
	 * Direct save function to save fallback image to block attributes
	 * Used when popover context might be destroyed (e.g., during media upload)
	 *
	 * @param {Object} fallbackImage - The fallback image object to save.
	 */
	const handleDirectSaveFallback = ( fallbackImage ) => {
		// if not core/image return early.
		if ( 'core/image' !== blockName ) {
			return;
		}

		// Get current dynamic image settings or create new ones
		const currentSettings = spectraProDynamicImage || {
			type: 'image',
			source: {
				type: settings.source.type || 'current_post',
				field: settings.source.field || 'featured_image',
			},
		};

		// Update with new fallback
		const updatedSettings = {
			...currentSettings,
			advanced: {
				...currentSettings.advanced,
				fallback: fallbackImage,
			},
		};

		setAttributes( { spectraProDynamicImage: updatedSettings } );
	};

	/**
	 * Determines the label for the apply/update button based on type and state.
	 *
	 * @return {string} The button label ('Add Image', 'Update Image', 'Apply', or 'Update').
	 */
	const getButtonLabel = () => {
		// Show loading state.
		if ( isLoading ) {
			if ( type === 'image' ) {
				return isEmpty( spectraProDynamicImage )
					? __( 'Adding Image…', 'spectra-pro' )
					: __( 'Updating Image…', 'spectra-pro' );
			}
			return isEmpty( spectraProDynamicImage ) ? __( 'Applying…', 'spectra-pro' ) : __( 'Updating…', 'spectra-pro' );
		}

		// Show normal state.
		if ( type === 'image' ) {
			return isEmpty( spectraProDynamicImage )
				? __( 'Add Image', 'spectra-pro' )
				: __( 'Update Image', 'spectra-pro' );
		}

		return isEmpty( spectraProDynamicImage ) ? __( 'Apply', 'spectra-pro' ) : __( 'Update', 'spectra-pro' );
	};

	return (
		<>
			{ error && (
				<Notice status="error" isDismissible onDismiss={ () => setError( null ) }>
					{ error }
				</Notice>
			) }

			{ /* Data Source Field ( like 'Current Post', 'Post', ) */ }
			<DataSourceField settings={ settings } updateSetting={ updateSetting } />

			{ /* Source Field with conditional additional fields */ }
			<SourceField settings={ settings } updateSetting={ updateSetting } />

			{ /* Fallback Image Field */ }
			<FallbackImage
				settings={ settings }
				updateAdvancedSetting={ updateAdvancedSetting }
				directSave={ handleDirectSaveFallback }
			/>

			{ /* Footer Buttons */ }
			<HStack justify="end">
				<Button variant="primary" onClick={ handleApply } isBusy={ isLoading } disabled={ isLoading || ! isValid() }>
					{ getButtonLabel() }
				</Button>
			</HStack>
		</>
	);
};

export default DynamicImage;
