/**
 * External dependencies.
 */
import { Notice, __experimentalVStack as VStack } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useState, useEffect } from '@wordpress/element';
import { applyFormat, removeFormat, getActiveFormat, insert, replace, create } from '@wordpress/rich-text';

/**
 * Internal dependencies.
 */
import { useDynamicContent } from '@spectra-pro-extensions/dynamic-content/context/DynamicContentContext';
import DataSourceField from '@spectra-pro-extensions/dynamic-content/components/Fields/DataSourceField';
import SourceField from '@spectra-pro-extensions/dynamic-content/components/Fields/SourceField';
import EnableLinkControl from '@spectra-pro-extensions/dynamic-content/components/Fields/EnableLinkControl';
import {
	DYNAMIC_CONTENT_ATTR,
	DYNAMIC_CONTENT_FORMAT_TYPE,
} from '@spectra-pro-extensions/dynamic-content/utils/constants';
import { getDynamicContent } from '@spectra-pro-extensions/dynamic-content/utils/helpers';
import ReplaceFullContentControl from '@spectra-pro-extensions/dynamic-content/components/Fields/ReplaceFullContentControl';
import AdvancedSettingsDrawer from '@spectra-pro-extensions/dynamic-content/components/Fields/AdvancedSettingsDrawer';
import DynamicContentFooter from '@spectra-pro-extensions/dynamic-content/components/DynamicContentFooter';

/**
 * Renders a popover for configuring dynamic content settings.
 *
 * Provides an interface to select data sources, fields, and advanced settings, and applies
 * or removes dynamic content formats to rich text content. Integrates with the dynamic
 * content context to manage settings and updates.
 *
 * @since x.x.x
 *
 * @param {Object}   props          - The component props.
 * @param {Object}   props.value    - The rich text value object from the block editor.
 * @param {Function} props.onChange - Callback to update the rich text value.
 * @param {Function} props.onClose  - Callback to close the popover.
 * @param {Object}   props.context  - Additional context for the dynamic content settings.
 * @return {Element} The dynamic content configuration popover.
 */
const DynamicContent = ( { value, onChange, onClose, context = {} } ) => {
	// Access dynamic content context for settings and updates.
	const { settings, updateSetting, updateAdvancedSetting, resetSettings } = useDynamicContent();

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

	// Extract selection start position and check for existing dynamic content format.
	const { start } = value;
	const existingFormat = getActiveFormat( value, DYNAMIC_CONTENT_FORMAT_TYPE );
	const hasExistingDynamicContent = !! existingFormat;

	// Sync existing dynamic content settings with context or reset settings.
	useEffect( () => {
		if ( hasExistingDynamicContent ) {
			try {
				// Parse existing dynamic content settings from format attributes.
				const jsonData = JSON.parse( existingFormat?.attributes[ DYNAMIC_CONTENT_ATTR ] || '{}' );

				// Update context with parsed settings.
				Object.entries( jsonData ).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 );
					}
				} );
			} catch ( e ) {}
		} else {
			resetSettings();
		}
	}, [ hasExistingDynamicContent, existingFormat, updateSetting, updateAdvancedSetting ] );

	/**
	 * Validates the current settings for dynamic content insertion.
	 *
	 * 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 'current_user':
				// If field is 'custom_field', must have userMetaKey.
				if ( source.field === 'custom_field' ) {
					return (
						!! source.userMetaKey && ( source.userMetaKey !== 'custom' || !! source.user_custom_meta_key )
					);
				}
				return true;

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

			case 'request_parameter':
				// Must have requestParamKey.
				return !! source.requestParamKey;

			default:
				// For other types (e.g., 'current_post', 'shortcode'), just need type and field.
				return true;
		}
	};

	/**
	 * Applies dynamic content settings to the rich text content.
	 *
	 * Fetches dynamic content, constructs a format object, and applies it to the text,
	 * either replacing the full content or inserting at the cursor position.
	 */
	const handleApply = async () => {
		if ( ! isValid ) return;

		setIsLoading( true );
		try {
			// Initialize formatData with required source properties.
			const formatData = {
				source: {
					type: settings.source.type,
					field: settings.source.field,
				},
			};

			// Helper to set properties if they exist.
			const setIfExists = ( target, key, val ) => {
				if ( val !== undefined && val !== '' ) target[ key ] = val;
			};

			// Source-specific configurations.
			const sourceHandlers = {
				post_type: () => {
					setIfExists( formatData.source, 'postId', settings.source.postId );
					setIfExists( formatData.source, 'postType', settings.source.postType );
				},
				current_user: () => {
					if ( settings.source.field === 'custom_input' ) {
						setIfExists( formatData.source, 'userMetaKey', settings.source.userMetaKey );
					}
				},
				request_parameter: () =>
					setIfExists( formatData.source, 'requestParamKey', settings.source.requestParamKey ),
				archive: () => {
					if ( settings.source.field === 'archive_meta' ) {
						setIfExists( formatData.source, 'archiveMetaKey', settings.source.archiveMetaKey );
					}
				},
			};

			// Field-specific configurations.
			const fieldHandlers = {
				featured_image: () =>
					setIfExists( formatData.source, 'featuredImageField', settings.source.featuredImageField ),
				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 );
					}
				},
				post_date: () => {
					setIfExists( formatData.source, 'dateType', settings.source.dateType );
					setIfExists( formatData.source, 'dateFormat', settings.source.dateFormat );
					if ( settings.source.dateFormat === 'custom' ) {
						setIfExists( formatData.source, 'customDateFormat', settings.source.customDateFormat );
					}
				},
				post_time: () => {
					setIfExists( formatData.source, 'timeType', settings.source.timeType );
					setIfExists( formatData.source, 'timeFormat', settings.source.timeFormat );
					if ( settings.source.timeFormat === 'custom' ) {
						setIfExists( formatData.source, 'customTimeFormat', settings.source.customTimeFormat );
					}
				},
				post_terms: () => {
					setIfExists( formatData.source, 'taxonomyType', settings.source.taxonomyType );
					setIfExists( formatData.source, 'taxonomyValueSeparator', settings.source.taxonomyValueSeparator );
				},
				comments_number: () => {
					setIfExists( formatData.source, 'noComments', settings.source.noComments );
					setIfExists( formatData.source, 'oneComment', settings.source.oneComment );
					setIfExists( formatData.source, 'manyComments', settings.source.manyComments );
				},
			};

			// Execute source and field handlers
			sourceHandlers[ settings.source.type ]?.();
			fieldHandlers[ settings.source.field ]?.();

			// Handle link settings if enabled.
			if ( settings.enableLink && settings.linkSource ) {
				formatData.enableLink = true;
				formatData.linkSource = settings.linkSource;

				if ( settings.linkSource === 'custom_field' ) {
					setIfExists( formatData, 'linkSourcePostCustomField', settings.linkSourcePostCustomField );
					if ( settings.linkSourcePostCustomField === 'custom' ) {
						setIfExists( formatData, 'linkSourcePostCustomMetaKey', settings.linkSourcePostCustomMetaKey );
					}
				} else if ( settings.linkSource === 'author_info' ) {
					setIfExists( formatData, 'linkSourceAuthorCustomField', settings.linkSourceAuthorCustomField );
					if ( settings.linkSourceAuthorCustomField === 'custom' ) {
						setIfExists( formatData, 'linkSourceAuthorMetaKey', settings.linkSourceAuthorMetaKey );
					}
				}
			}

			// Include non-empty advanced settings.
			const advancedKeys = [ 'before', 'after', 'charLength', 'fallback' ];
			const advanced = Object.fromEntries(
				advancedKeys
					.filter( ( key ) => settings.advanced?.[ key ] )
					.map( ( key ) => [ key, settings.advanced[ key ] ] )
			);
			if ( Object.keys( advanced ).length ) formatData.advanced = advanced;

			// Create format object for rich text.
			const format = {
				type: DYNAMIC_CONTENT_FORMAT_TYPE,
				attributes: {
					[ DYNAMIC_CONTENT_ATTR ]: JSON.stringify( formatData ),
				},
			};

			// Update postId if current_post is selected based on the context.
			if ( 'current_post' === settings.source.type && context?.postType && context?.postId ) {
				updateSetting( 'source.postId', context.postId );
				updateSetting( 'source.postType', context.postType );
			}

			// Fetch dynamic content based on settings.
			const dynamicContent = await getDynamicContent( settings );

			let newValue;
			if ( settings.replaceFullContent ) {
				// Replace entire content with dynamic content.
				const newContent = create( { text: dynamicContent } );
				// Replace all content and apply format to the entire new content.
				newValue = replace( value, value.text, newContent.text );
				newValue = applyFormat( newValue, format, 0, newValue.text.length );
				// Add zero-width space at end and move cursor there.
				newValue = insert( newValue, '\u200B', newValue.text.length );
				newValue.start = newValue.text.length;
				newValue.end = newValue.text.length;
			} else if ( hasExistingDynamicContent ) {
				// Get the current format's attributes to identify it.
				const currentFormatAttributes = existingFormat?.attributes?.[ DYNAMIC_CONTENT_ATTR ];

				// Find the boundaries of THIS specific dynamic content format.
				let formatStart = value.start;
				let formatEnd = value.end;

				// Make sure we're within a format.
				if ( value.formats && value.formats[ value.start ] ) {
					// Search backwards for the start of THIS format.
					while ( formatStart > 0 ) {
						const formats = value.formats[ formatStart - 1 ];
						if ( formats ) {
							const hasSameFormat = formats.some(
								( f ) =>
									f.type === DYNAMIC_CONTENT_FORMAT_TYPE &&
									f.attributes?.[ DYNAMIC_CONTENT_ATTR ] === currentFormatAttributes
							);
							if ( hasSameFormat ) {
								formatStart--;
							} else {
								break;
							}
						} else {
							break;
						}
					}

					// Search forwards for the end of THIS format.
					while ( formatEnd < value.text.length ) {
						const formats = value.formats[ formatEnd ];
						if ( formats ) {
							const hasSameFormat = formats.some(
								( f ) =>
									f.type === DYNAMIC_CONTENT_FORMAT_TYPE &&
									f.attributes?.[ DYNAMIC_CONTENT_ATTR ] === currentFormatAttributes
							);
							if ( hasSameFormat ) {
								formatEnd++;
							} else {
								break;
							}
						} else {
							break;
						}
					}
				}

				// Create a new value with the old text.
				newValue = create( {
					html: value.text.substring( 0, formatStart ) + dynamicContent + value.text.substring( formatEnd ),
				} );

				// Copy over all formats EXCEPT the one we're updating.
				if ( value.formats ) {
					newValue.formats = [];
					for ( let i = 0; i < newValue.text.length; i++ ) {
						if ( i < formatStart ) {
							// Before the updated content - keep original formats.
							newValue.formats[ i ] = value.formats[ i ] || [];
						} else if ( i >= formatStart + dynamicContent.length ) {
							// After the updated content - shift the original formats.
							const originalIndex = i - dynamicContent.length + ( formatEnd - formatStart );
							newValue.formats[ i ] = value.formats[ originalIndex ] || [];
						}
					}
				}

				// Apply the updated format to the new content.
				newValue = applyFormat( newValue, format, formatStart, formatStart + dynamicContent.length );
			} else {
				// Apply new dynamic content.
				const contentToInsert = create( { text: dynamicContent } );
				// Insert at current position and apply format.
				newValue = insert( value, contentToInsert, start );
				// Apply format to the inserted content.
				const insertedLength = contentToInsert.text.length;
				newValue = applyFormat( newValue, format, start, start + insertedLength );
				// Add zero-width space after and move cursor there.
				newValue = insert( newValue, '\u200B', start + insertedLength );
				newValue.start = start + insertedLength + 1;
				newValue.end = start + insertedLength + 1;
			}

			// Update rich text value.
			onChange( newValue );

			// Close popover and reset settings.
			// Pass true to indicate this is after applying changes
			onClose( true );
			resetSettings();
		} catch ( e ) {
			setError( __( 'Failed to apply dynamic content.', 'spectra-pro' ) );
		} finally {
			setIsLoading( false );
		}
	};

	/**
	 * Removes existing dynamic content format from the rich text.
	 *
	 * Clears the format, updates the rich text value, and closes the popover.
	 */
	const handleRemove = () => {
		if ( hasExistingDynamicContent ) {
			const newValue = removeFormat( value, DYNAMIC_CONTENT_FORMAT_TYPE );
			onChange( newValue );
			resetSettings();
			onClose();
		}
	};

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

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

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

			{ /* Replace Full Content Control */ }
			<ReplaceFullContentControl settings={ settings } updateSetting={ updateSetting } />

			{ /* Enable Link Control */ }
			<EnableLinkControl settings={ settings } updateSetting={ updateSetting } />

			{ /* Advanced Settings Panel */ }
			<AdvancedSettingsDrawer
				settings={ settings }
				updateAdvancedSetting={ updateAdvancedSetting }
				settingsOpen={ settingsOpen }
				setSettingsOpen={ setSettingsOpen }
			/>

			{ /* Footer Buttons */ }
			<DynamicContentFooter
				hasExistingDynamicContent={ hasExistingDynamicContent }
				isLoading={ isLoading }
				onRemove={ handleRemove }
				onApply={ handleApply }
			/>
		</VStack>
	);
};

export default DynamicContent;
